mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Merge branch 'v5-develop' into acss
This commit is contained in:
commit
573c82ed95
91
app/Console/Commands/BackupUpdate.php
Normal file
91
app/Console/Commands/BackupUpdate.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Design;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use stdClass;
|
||||
|
||||
class BackupUpdate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:backup-update';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Shift backups from DB to storage';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//always return state to first DB
|
||||
|
||||
$current_db = config('database.default');
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$this->handleOnDb();
|
||||
} else {
|
||||
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$this->handleOnDb();
|
||||
}
|
||||
|
||||
MultiDB::setDB($current_db);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function handleOnDb()
|
||||
{
|
||||
|
||||
Backup::whereHas('activity')->whereNotNull('html_backup')->cursor()->each(function($backup){
|
||||
|
||||
if($backup->activity->client()->exists()){
|
||||
|
||||
$client = $backup->activity->client;
|
||||
$backup->storeRemotely($backup->html_backup, $client);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
@ -126,15 +126,13 @@ class CompanySettings extends BaseSettings
|
||||
public $auto_bill = 'off'; //off,always,optin,optout //@implemented
|
||||
public $auto_bill_date = 'on_due_date'; // on_due_date , on_send_date //@implemented
|
||||
|
||||
//public $design = 'views/pdf/design1.blade.php'; //@deprecated - never used
|
||||
|
||||
public $invoice_terms = ''; //@implemented
|
||||
public $quote_terms = ''; //@implemented
|
||||
public $invoice_taxes = 0; // ? used in AP only?
|
||||
// public $enabled_item_tax_rates = 0;
|
||||
public $invoice_design_id = 'VolejRejNm'; //@implemented
|
||||
public $quote_design_id = 'VolejRejNm'; //@implemented
|
||||
public $credit_design_id = 'VolejRejNm'; //@implemented
|
||||
|
||||
public $invoice_design_id = 'Wpmbk5ezJn'; //@implemented
|
||||
public $quote_design_id = 'Wpmbk5ezJn'; //@implemented
|
||||
public $credit_design_id = 'Wpmbk5ezJn'; //@implemented
|
||||
public $invoice_footer = ''; //@implemented
|
||||
public $credit_footer = ''; //@implemented
|
||||
public $credit_terms = ''; //@implemented
|
||||
@ -146,7 +144,6 @@ class CompanySettings extends BaseSettings
|
||||
public $tax_name3 = ''; //@TODO where do we use this?
|
||||
public $tax_rate3 = 0; //@TODO where do we use this?
|
||||
public $payment_type_id = '0'; //@TODO where do we use this?
|
||||
// public $invoice_fields = ''; //@TODO is this redundant, we store this in the custom_fields on the company?
|
||||
|
||||
public $valid_until = ''; //@implemented
|
||||
|
||||
|
@ -20,6 +20,7 @@ use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use stdClass;
|
||||
|
||||
@ -136,19 +137,33 @@ class ActivityController extends BaseController
|
||||
public function downloadHistoricalEntity(DownloadHistoricalEntityRequest $request, Activity $activity)
|
||||
{
|
||||
$backup = $activity->backup;
|
||||
$html_backup = '';
|
||||
|
||||
if (! $backup || ! $backup->html_backup) {
|
||||
/* Refactor 20-10-2021
|
||||
*
|
||||
* We have moved the backups out of the database and into object storage.
|
||||
* In order to handle edge cases, we still check for the database backup
|
||||
* in case the file no longer exists
|
||||
*/
|
||||
|
||||
if($backup && $backup->filename && Storage::disk(config('filesystems.default'))->exists($backup->filename)){ //disk
|
||||
$html_backup = file_get_contents(Storage::disk(config('filesystems.default'))->path($backup->filename));
|
||||
}
|
||||
elseif($backup && $backup->html_backup){ //db
|
||||
$html_backup = $backup->html_backup;
|
||||
}
|
||||
elseif (! $backup || ! $backup->html_backup) { //failed
|
||||
return response()->json(['message'=> ctrans('texts.no_backup_exists'), 'errors' => new stdClass], 404);
|
||||
}
|
||||
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
$pdf = (new Phantom)->convertHtmlToPdf($backup->html_backup);
|
||||
$pdf = (new Phantom)->convertHtmlToPdf($html_backup);
|
||||
}
|
||||
elseif(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($backup->html_backup);
|
||||
$pdf = (new NinjaPdf())->build($html_backup);
|
||||
}
|
||||
else {
|
||||
$pdf = $this->makePdf(null, null, $backup->html_backup);
|
||||
$pdf = $this->makePdf(null, null, $html_backup);
|
||||
}
|
||||
|
||||
if (isset($activity->invoice_id)) {
|
||||
|
@ -738,6 +738,10 @@ class BaseController extends Controller
|
||||
return redirect()->secure(request()->getRequestUri());
|
||||
}
|
||||
|
||||
/* Clean up URLs and remove query parameters from the URL*/
|
||||
if(request()->has('login') && request()->input('login') == 'true')
|
||||
return redirect('/')->with(['login' => "true"]);
|
||||
|
||||
$data = [];
|
||||
|
||||
//pass report errors bool to front end
|
||||
@ -748,6 +752,9 @@ class BaseController extends Controller
|
||||
$data['build'] = request()->has('build') ? request()->input('build') : '';
|
||||
$data['login'] = request()->has('login') ? request()->input('login') : "false";
|
||||
|
||||
if(request()->session()->has('login'))
|
||||
$data['login'] = "true";
|
||||
|
||||
$data['user_agent'] = request()->server('HTTP_USER_AGENT');
|
||||
|
||||
$data['path'] = $this->setBuild();
|
||||
|
@ -149,11 +149,11 @@ class PaymentMethodController extends Controller
|
||||
private function getClientGateway()
|
||||
{
|
||||
if (request()->query('method') == GatewayType::CREDIT_CARD) {
|
||||
return $gateway = auth()->user()->client->getCreditCardGateway();
|
||||
return auth()->user()->client->getCreditCardGateway();
|
||||
}
|
||||
|
||||
if (request()->query('method') == GatewayType::BANK_TRANSFER) {
|
||||
return $gateway = auth()->user()->client->getBankTransferGateway();
|
||||
if (in_array(request()->query('method'), [GatewayType::BANK_TRANSFER, GatewayType::DIRECT_DEBIT, GatewayType::SEPA])) {
|
||||
return auth()->user()->client->getBankTransferGateway();
|
||||
}
|
||||
|
||||
abort(404, 'Gateway not found.');
|
||||
|
@ -18,6 +18,8 @@ use App\Http\Requests\GroupSetting\EditGroupSettingRequest;
|
||||
use App\Http\Requests\GroupSetting\ShowGroupSettingRequest;
|
||||
use App\Http\Requests\GroupSetting\StoreGroupSettingRequest;
|
||||
use App\Http\Requests\GroupSetting\UpdateGroupSettingRequest;
|
||||
use App\Http\Requests\GroupSetting\UploadGroupSettingRequest;
|
||||
use App\Models\Account;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Repositories\GroupSettingRepository;
|
||||
use App\Transformers\GroupSettingTransformer;
|
||||
@ -497,4 +499,68 @@ class GroupSettingController extends BaseController
|
||||
|
||||
return $this->listResponse(GroupSetting::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UploadGroupSettingRequest $request
|
||||
* @param GroupSetting $group_setting
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Put(
|
||||
* path="/api/v1/group_settings/{id}/upload",
|
||||
* operationId="uploadGroupSetting",
|
||||
* tags={"group_settings"},
|
||||
* summary="Uploads a document to a group setting",
|
||||
* description="Handles the uploading of a document to a group setting",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Group Setting Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the Group Setting object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Invoice"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function upload(UploadGroupSettingRequest $request, GroupSetting $group_setting)
|
||||
{
|
||||
if(!$this->checkFeature(Account::FEATURE_DOCUMENTS))
|
||||
return $this->featureFailure();
|
||||
|
||||
if ($request->has('documents'))
|
||||
$this->saveDocuments($request->file('documents'), $group_setting);
|
||||
|
||||
return $this->itemResponse($group_setting->fresh());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -180,6 +180,25 @@ class MigrationController extends BaseController
|
||||
$company->vendors()->forceDelete();
|
||||
$company->expenses()->forceDelete();
|
||||
|
||||
$settings = $company->settings;
|
||||
|
||||
/* Reset all counters to 1 after a purge */
|
||||
$settings->recurring_invoice_number_counter = 1;
|
||||
$settings->invoice_number_counter = 1;
|
||||
$settings->quote_number_counter = 1;
|
||||
$settings->client_number_counter = 1;
|
||||
$settings->credit_number_counter = 1;
|
||||
$settings->task_number_counter = 1;
|
||||
$settings->expense_number_counter = 1;
|
||||
$settings->recurring_expense_number_counter = 1;
|
||||
$settings->recurring_quote_number_counter = 1;
|
||||
$settings->vendor_number_counter = 1;
|
||||
$settings->ticket_number_counter = 1;
|
||||
$settings->payment_number_counter = 1;
|
||||
$settings->project_number_counter = 1;
|
||||
|
||||
$company->settings = $settings;
|
||||
|
||||
$company->save();
|
||||
|
||||
return response()->json(['message' => 'Settings preserved'], 200);
|
||||
|
47
app/Http/Controllers/OpenAPI/RecurringExpense.php
Normal file
47
app/Http/Controllers/OpenAPI/RecurringExpense.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="RecurringExpense",
|
||||
* type="object",
|
||||
* @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"),
|
||||
* @OA\Property(property="user_id", type="string", example="", description="__________"),
|
||||
* @OA\Property(property="assigned_user_id", type="string", example="", description="__________"),
|
||||
* @OA\Property(property="company_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="client_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="invoice_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="bank_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="invoice_currency_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="expense_currency_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="invoice_category_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="payment_type_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="recurring_expense_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="private_notes", type="string", example="", description="________"),
|
||||
* @OA\Property(property="public_notes", type="string", example="", description="________"),
|
||||
* @OA\Property(property="transaction_reference", type="string", example="", description="________"),
|
||||
* @OA\Property(property="transcation_id", type="string", example="", description="________"),
|
||||
* @OA\Property(property="custom_value1", type="string", example="", description="________"),
|
||||
* @OA\Property(property="custom_value2", type="string", example="", description="________"),
|
||||
* @OA\Property(property="custom_value3", type="string", example="", description="________"),
|
||||
* @OA\Property(property="custom_value4", type="string", example="", description="________"),
|
||||
* @OA\Property(property="tax_name1", type="string", example="", description="________"),
|
||||
* @OA\Property(property="tax_name2", type="string", example="", description="________"),
|
||||
* @OA\Property(property="tax_rate1", type="number", format="float", example="10.00", description="_________"),
|
||||
* @OA\Property(property="tax_rate2", type="number", format="float", example="10.00", description="_________"),
|
||||
* @OA\Property(property="tax_name3", type="string", example="", description="________"),
|
||||
* @OA\Property(property="tax_rate3", type="number", format="float", example="10.00", description="_________"),
|
||||
* @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"),
|
||||
* @OA\Property(property="frequency_id", type="number", format="int", example="1", description="_________"),
|
||||
* @OA\Property(property="remaining_cycles", type="number", format="int", example="1", description="_________"),
|
||||
* @OA\Property(property="foreign_amount", type="number", format="float", example="10.00", description="_________"),
|
||||
* @OA\Property(property="exchange_rate", type="number", format="float", example="0.80", description="_________"),
|
||||
* @OA\Property(property="date", type="string", example="", description="________"),
|
||||
* @OA\Property(property="payment_date", type="string", example="", description="________"),
|
||||
* @OA\Property(property="should_be_invoiced", type="boolean", example=true, description="_________"),
|
||||
* @OA\Property(property="is_deleted", type="boolean", example=true, description="_________"),
|
||||
* @OA\Property(property="last_sent_date", type="string", format="date", example="1994-07-30", description="The Date it was sent last"),
|
||||
* @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The next send date"),
|
||||
* @OA\Property(property="invoice_documents", type="boolean", example=true, description=""),
|
||||
* @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"),
|
||||
* @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"),
|
||||
* )
|
||||
*/
|
@ -10,8 +10,8 @@
|
||||
* email="contact@invoiceninja.com"
|
||||
* ),
|
||||
* @OA\License(
|
||||
* name="Attribution Assurance License",
|
||||
* url="https://opensource.org/licenses/AAL"
|
||||
* name="Elastic License",
|
||||
* url="https://www.elastic.co/licensing/elastic-license"
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Server(
|
||||
|
39
app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php
Normal file
39
app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\GroupSetting;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class UploadGroupSettingRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->group_setting);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
|
||||
$rules = [];
|
||||
|
||||
if($this->input('documents'))
|
||||
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
|
||||
|
||||
return $rules;
|
||||
|
||||
}
|
||||
}
|
@ -70,6 +70,13 @@ class StoreInvoiceRequest extends Request
|
||||
$input['amount'] = 0;
|
||||
$input['balance'] = 0;
|
||||
|
||||
if(array_key_exists('tax_rate1', $input) && is_null($input['tax_rate1']))
|
||||
$input['tax_rate1'] = 0;
|
||||
if(array_key_exists('tax_rate2', $input) && is_null($input['tax_rate2']))
|
||||
$input['tax_rate2'] = 0;
|
||||
if(array_key_exists('tax_rate3', $input) && is_null($input['tax_rate3']))
|
||||
$input['tax_rate3'] = 0;
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class StoreUserRequest extends Request
|
||||
}
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$rules['hosted_users'] = new CanAddUserRule(auth()->user()->company()->account);
|
||||
$rules['id'] = new CanAddUserRule();
|
||||
}
|
||||
|
||||
return $rules;
|
||||
|
@ -18,11 +18,9 @@ use Illuminate\Contracts\Validation\Rule;
|
||||
*/
|
||||
class CanAddUserRule implements Rule
|
||||
{
|
||||
public $account;
|
||||
|
||||
public function __construct($account)
|
||||
public function __construct()
|
||||
{
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,7 +30,7 @@ class CanAddUserRule implements Rule
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
return $this->account->users->count() < $this->account->num_users;
|
||||
return auth()->user()->company()->account->users->count() < auth()->user()->company()->account->num_users;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,6 +38,6 @@ class CanAddUserRule implements Rule
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return ctrans('texts.limit_users', ['limit' => $this->account->num_users]);
|
||||
return ctrans('texts.limit_users', ['limit' => auth()->user()->company()->account->num_users]);
|
||||
}
|
||||
}
|
||||
|
@ -887,6 +887,7 @@ class CompanyImport implements ShouldQueue
|
||||
[
|
||||
'hashed_id',
|
||||
'company_id',
|
||||
'backup',
|
||||
],
|
||||
[
|
||||
['users' => 'user_id'],
|
||||
@ -1192,6 +1193,10 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
if(array_key_exists('deleted_at', $obj_array) && $obj_array['deleted_at'] > 1){
|
||||
$obj_array['deleted_at'] = now();
|
||||
}
|
||||
|
||||
$activity_invitation_key = false;
|
||||
|
||||
if($class == 'App\Models\Activity'){
|
||||
@ -1270,6 +1275,10 @@ class CompanyImport implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists('deleted_at', $obj_array) && $obj_array['deleted_at'] > 1){
|
||||
$obj_array['deleted_at'] = now();
|
||||
}
|
||||
|
||||
/* New to convert product ids from old hashes to new hashes*/
|
||||
if($class == 'App\Models\Subscription'){
|
||||
$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
|
||||
@ -1320,6 +1329,10 @@ class CompanyImport implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists('deleted_at', $obj_array) && $obj_array['deleted_at'] > 1){
|
||||
$obj_array['deleted_at'] = now();
|
||||
}
|
||||
|
||||
/* New to convert product ids from old hashes to new hashes*/
|
||||
if($class == 'App\Models\Subscription'){
|
||||
//$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
|
||||
|
@ -110,6 +110,16 @@ class SendRecurring implements ShouldQueue
|
||||
|
||||
$this->recurring_invoice->save();
|
||||
|
||||
/*
|
||||
|
||||
if ($this->recurring_invoice->company->pause_recurring_until_paid){
|
||||
$this->recurring_invoice->service()
|
||||
->stop();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
//Admin notification for recurring invoice sent.
|
||||
if ($invoice->invitations->count() >= 1 ) {
|
||||
$invoice->entityEmailEvent($invoice->invitations->first(), 'invoice', 'email_template_invoice');
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Client;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class Backup extends BaseModel
|
||||
{
|
||||
public function getEntityType()
|
||||
@ -22,4 +25,26 @@ class Backup extends BaseModel
|
||||
{
|
||||
return $this->belongsTo(Activity::class);
|
||||
}
|
||||
|
||||
public function storeRemotely(string $html, Client $client)
|
||||
{
|
||||
|
||||
if(strlen($html) == 0)
|
||||
return;
|
||||
|
||||
$path = $client->backup_path() . "/";
|
||||
$filename = now()->format('Y_m_d'). "_" . md5(time()) . ".html";
|
||||
$file_path = $path . $filename;
|
||||
|
||||
Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
|
||||
|
||||
Storage::disk(config('filesystems.default'))->put($file_path, $html);
|
||||
|
||||
if(Storage::disk(config('filesystems.default'))->exists($file_path)){
|
||||
$this->html_backup = '';
|
||||
$this->filename = $file_path;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -518,6 +518,30 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
|
||||
}
|
||||
|
||||
if ($this->currency()->code == 'EUR' && in_array(GatewayType::SEPA, array_column($pms, 'gateway_type_id'))) {
|
||||
foreach ($pms as $pm) {
|
||||
if ($pm['gateway_type_id'] == GatewayType::SEPA) {
|
||||
$cg = CompanyGateway::find($pm['company_gateway_id']);
|
||||
|
||||
if ($cg && $cg->fees_and_limits->{GatewayType::SEPA}->is_enabled) {
|
||||
return $cg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->country->iso_3166_3 == 'GBR' && in_array(GatewayType::DIRECT_DEBIT, array_column($pms, 'gateway_type_id'))) {
|
||||
foreach ($pms as $pm) {
|
||||
if ($pm['gateway_type_id'] == GatewayType::DIRECT_DEBIT) {
|
||||
$cg = CompanyGateway::find($pm['company_gateway_id']);
|
||||
|
||||
if ($cg && $cg->fees_and_limits->{GatewayType::DIRECT_DEBIT}->is_enabled) {
|
||||
return $cg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
@ -532,6 +556,10 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
if ($this->currency()->code == 'EUR') {
|
||||
return GatewayType::SEPA;
|
||||
}
|
||||
|
||||
if ($this->currency()->code == 'GBP') {
|
||||
return GatewayType::DIRECT_DEBIT;
|
||||
}
|
||||
}
|
||||
|
||||
public function getCurrencyCode()
|
||||
@ -717,6 +745,12 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
})->first()->locale;
|
||||
}
|
||||
|
||||
public function backup_path()
|
||||
{
|
||||
return $this->company->company_key.'/'.$this->client_hash.'/backups';
|
||||
}
|
||||
|
||||
|
||||
public function invoice_filepath($invitation)
|
||||
{
|
||||
$contact_key = $invitation->contact->contact_key;
|
||||
|
@ -155,7 +155,9 @@ class Gateway extends StaticModel
|
||||
break;
|
||||
case 52:
|
||||
return [
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']] // GoCardless
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']], // GoCardless
|
||||
GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']],
|
||||
GatewayType::SEPA => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']]
|
||||
];
|
||||
break;
|
||||
case 58:
|
||||
|
@ -34,6 +34,7 @@ class GatewayType extends StaticModel
|
||||
const GIROPAY = 15;
|
||||
const PRZELEWY24 = 16;
|
||||
const EPS = 17;
|
||||
const DIRECT_DEBIT = 18;
|
||||
const ACSS = 19;
|
||||
const BECS = 20;
|
||||
|
||||
@ -86,6 +87,8 @@ class GatewayType extends StaticModel
|
||||
return ctrans('tets.becs');
|
||||
case self::ACSS:
|
||||
return ctrans('texts.acss');
|
||||
case self::DIRECT_DEBIT:
|
||||
return ctrans('texts.payment_type_direct_debit');
|
||||
default:
|
||||
return 'Undefined.';
|
||||
break;
|
||||
|
@ -50,7 +50,8 @@ class PaymentType extends StaticModel
|
||||
const GIROPAY = 39;
|
||||
const PRZELEWY24 = 40;
|
||||
const EPS = 41;
|
||||
const BECS = 43;
|
||||
const DIRECT_DEBIT = 42;
|
||||
` const BECS = 43;
|
||||
const ACSS = 44;
|
||||
|
||||
public static function parseCardType($cardName)
|
||||
|
@ -140,7 +140,8 @@ class PayPal
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_BRAINTREE,
|
||||
$this->braintree->client
|
||||
$this->braintree->client,
|
||||
$this->braintree->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->braintree->encodePrimaryKey($payment->id)]);
|
||||
|
@ -56,6 +56,7 @@ class ACH implements MethodInterface
|
||||
try {
|
||||
$redirect = $this->go_cardless->gateway->redirectFlows()->create([
|
||||
"params" => [
|
||||
"scheme" => "ach",
|
||||
"session_token" => $session_token,
|
||||
"success_redirect_url" => route('client.payment_methods.confirm', [
|
||||
'method' => GatewayType::BANK_TRANSFER,
|
||||
|
248
app/PaymentDrivers/GoCardless/DirectDebit.php
Normal file
248
app/PaymentDrivers/GoCardless/DirectDebit.php
Normal file
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\GoCardless;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Jobs\Mail\PaymentFailureMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\PaymentDrivers\GoCardlessPaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class DirectDebit implements MethodInterface
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected GoCardlessPaymentDriver $go_cardless;
|
||||
|
||||
public function __construct(GoCardlessPaymentDriver $go_cardless)
|
||||
{
|
||||
$this->go_cardless = $go_cardless;
|
||||
|
||||
$this->go_cardless->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authorization for Direct Debit.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Redirector|RedirectResponse|void
|
||||
*/
|
||||
public function authorizeView(array $data)
|
||||
{
|
||||
$session_token = \Illuminate\Support\Str::uuid()->toString();
|
||||
|
||||
try {
|
||||
$redirect = $this->go_cardless->gateway->redirectFlows()->create([
|
||||
'params' => [
|
||||
'session_token' => $session_token,
|
||||
'success_redirect_url' => route('client.payment_methods.confirm', [
|
||||
'method' => GatewayType::DIRECT_DEBIT,
|
||||
'session_token' => $session_token,
|
||||
]),
|
||||
'prefilled_customer' => [
|
||||
'given_name' => auth('contact')->user()->first_name,
|
||||
'family_name' => auth('contact')->user()->last_name,
|
||||
'email' => auth('contact')->user()->email,
|
||||
'address_line1' => auth('contact')->user()->client->address1,
|
||||
'city' => auth('contact')->user()->client->city,
|
||||
'postal_code' => auth('contact')->user()->client->postal_code,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return redirect(
|
||||
$redirect->redirect_url
|
||||
);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->processUnsuccessfulAuthorization($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unsuccessful authorization.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @throws PaymentFailed
|
||||
* @return void
|
||||
*/
|
||||
public function processUnsuccessfulAuthorization(\Exception $exception): void
|
||||
{
|
||||
SystemLogger::dispatch(
|
||||
$exception->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_GOCARDLESS,
|
||||
$this->go_cardless->client,
|
||||
$this->go_cardless->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($exception->getMessage(), $exception->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authorization response for Direct Debit.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse|void
|
||||
*/
|
||||
public function authorizeResponse(Request $request)
|
||||
{
|
||||
try {
|
||||
$redirect_flow = $this->go_cardless->gateway->redirectFlows()->complete(
|
||||
$request->redirect_flow_id,
|
||||
['params' => [
|
||||
'session_token' => $request->session_token
|
||||
]],
|
||||
);
|
||||
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->brand = ctrans('texts.payment_type_direct_debit');
|
||||
$payment_meta->type = GatewayType::DIRECT_DEBIT;
|
||||
$payment_meta->state = 'authorized';
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $redirect_flow->links->mandate,
|
||||
'payment_method_id' => GatewayType::DIRECT_DEBIT,
|
||||
];
|
||||
|
||||
$payment_method = $this->go_cardless->storeGatewayToken($data, ['gateway_customer_reference' => $redirect_flow->links->customer]);
|
||||
|
||||
return redirect()->route('client.payment_methods.show', $payment_method->hashed_id);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->processUnsuccessfulAuthorization($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment view for Direct Debit.
|
||||
*
|
||||
* @param array $data
|
||||
* @return View
|
||||
*/
|
||||
public function paymentView(array $data): View
|
||||
{
|
||||
$data['gateway'] = $this->go_cardless;
|
||||
$data['amount'] = $this->go_cardless->convertToGoCardlessAmount($data['total']['amount_with_fee'], $this->go_cardless->client->currency()->precision);
|
||||
$data['currency'] = $this->go_cardless->client->getCurrencyCode();
|
||||
|
||||
return render('gateways.gocardless.direct_debit.pay', $data);
|
||||
}
|
||||
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
$token = ClientGatewayToken::find(
|
||||
$this->decodePrimaryKey($request->source)
|
||||
)->firstOrFail();
|
||||
|
||||
try {
|
||||
$payment = $this->go_cardless->gateway->payments()->create([
|
||||
'params' => [
|
||||
'amount' => $request->amount,
|
||||
'currency' => $request->currency,
|
||||
'metadata' => [
|
||||
'payment_hash' => $this->go_cardless->payment_hash->hash,
|
||||
],
|
||||
'links' => [
|
||||
'mandate' => $token->token,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
if ($payment->status === 'pending_submission') {
|
||||
return $this->processPendingPayment($payment, ['token' => $token->hashed_id]);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment($payment);
|
||||
} catch (\Exception $exception) {
|
||||
throw new PaymentFailed($exception->getMessage(), $exception->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle pending payments for Direct Debit.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @param array $data
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processPendingPayment(\GoCardlessPro\Resources\Payment $payment, array $data = [])
|
||||
{
|
||||
$data = [
|
||||
'payment_method' => $data['token'],
|
||||
'payment_type' => PaymentType::DIRECT_DEBIT,
|
||||
'amount' => $this->go_cardless->payment_hash->data->amount_with_fee,
|
||||
'transaction_reference' => $payment->id,
|
||||
'gateway_type_id' => GatewayType::DIRECT_DEBIT,
|
||||
];
|
||||
|
||||
$payment = $this->go_cardless->createPayment($data, Payment::STATUS_PENDING);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_GOCARDLESS,
|
||||
$this->go_cardless->client,
|
||||
$this->go_cardless->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->go_cardless->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process unsuccessful payments for Direct Debit.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @return never
|
||||
*/
|
||||
public function processUnsuccessfulPayment(\GoCardlessPro\Resources\Payment $payment)
|
||||
{
|
||||
PaymentFailureMailer::dispatch($this->go_cardless->client, $payment->status, $this->go_cardless->client->company, $this->go_cardless->payment_hash->data->amount_with_fee);
|
||||
|
||||
PaymentFailureMailer::dispatch(
|
||||
$this->go_cardless->client,
|
||||
$payment,
|
||||
$this->go_cardless->client->company,
|
||||
$payment->amount
|
||||
);
|
||||
|
||||
$message = [
|
||||
'server_response' => $payment,
|
||||
'data' => $this->go_cardless->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_GOCARDLESS,
|
||||
$this->go_cardless->client,
|
||||
$this->go_cardless->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed('Failed to process the payment.', 500);
|
||||
}
|
||||
}
|
250
app/PaymentDrivers/GoCardless/SEPA.php
Normal file
250
app/PaymentDrivers/GoCardless/SEPA.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\GoCardless;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\PaymentDrivers\GoCardlessPaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Exception;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SEPA implements MethodInterface
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected GoCardlessPaymentDriver $go_cardless;
|
||||
|
||||
public function __construct(GoCardlessPaymentDriver $go_cardless)
|
||||
{
|
||||
$this->go_cardless = $go_cardless;
|
||||
|
||||
$this->go_cardless->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authorization for SEPA.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Redirector|RedirectResponse|void
|
||||
*/
|
||||
public function authorizeView(array $data)
|
||||
{
|
||||
$session_token = \Illuminate\Support\Str::uuid()->toString();
|
||||
|
||||
try {
|
||||
$redirect = $this->go_cardless->gateway->redirectFlows()->create([
|
||||
'params' => [
|
||||
'scheme' => 'sepa_core',
|
||||
'session_token' => $session_token,
|
||||
'success_redirect_url' => route('client.payment_methods.confirm', [
|
||||
'method' => GatewayType::SEPA,
|
||||
'session_token' => $session_token,
|
||||
]),
|
||||
'prefilled_customer' => [
|
||||
'given_name' => auth('contact')->user()->first_name,
|
||||
'family_name' => auth('contact')->user()->last_name,
|
||||
'email' => auth('contact')->user()->email,
|
||||
'address_line1' => auth('contact')->user()->client->address1,
|
||||
'city' => auth('contact')->user()->client->city,
|
||||
'postal_code' => auth('contact')->user()->client->postal_code,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return redirect(
|
||||
$redirect->redirect_url
|
||||
);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->processUnsuccessfulAuthorization($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unsuccessful authorization for SEPA.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @return void
|
||||
*/
|
||||
public function processUnsuccessfulAuthorization(\Exception $exception): void
|
||||
{
|
||||
$this->go_cardless->sendFailureMail($exception->getMessage());
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$exception->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_GOCARDLESS,
|
||||
$this->go_cardless->client,
|
||||
$this->go_cardless->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($exception->getMessage(), $exception->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authorization response for SEPA.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse|void
|
||||
*/
|
||||
public function authorizeResponse(Request $request)
|
||||
{
|
||||
try {
|
||||
$redirect_flow = $this->go_cardless->gateway->redirectFlows()->complete(
|
||||
$request->redirect_flow_id,
|
||||
['params' => [
|
||||
'session_token' => $request->session_token
|
||||
]],
|
||||
);
|
||||
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->brand = ctrans('texts.sepa');
|
||||
$payment_meta->type = GatewayType::SEPA;
|
||||
$payment_meta->state = 'authorized';
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $redirect_flow->links->mandate,
|
||||
'payment_method_id' => GatewayType::SEPA,
|
||||
];
|
||||
|
||||
$payment_method = $this->go_cardless->storeGatewayToken($data, ['gateway_customer_reference' => $redirect_flow->links->customer]);
|
||||
|
||||
return redirect()->route('client.payment_methods.show', $payment_method->hashed_id);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->processUnsuccessfulAuthorization($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment view for SEPA.
|
||||
*
|
||||
* @param array $data
|
||||
* @return View
|
||||
*/
|
||||
public function paymentView(array $data): View
|
||||
{
|
||||
$data['gateway'] = $this->go_cardless;
|
||||
$data['amount'] = $this->go_cardless->convertToGoCardlessAmount($data['total']['amount_with_fee'], $this->go_cardless->client->currency()->precision);
|
||||
$data['currency'] = $this->go_cardless->client->getCurrencyCode();
|
||||
|
||||
return render('gateways.gocardless.sepa.pay', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the payment page for SEPA.
|
||||
*
|
||||
* @param PaymentResponseRequest $request
|
||||
* @return RedirectResponse|App\PaymentDrivers\GoCardless\never|void
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
$token = ClientGatewayToken::find(
|
||||
$this->decodePrimaryKey($request->source)
|
||||
)->firstOrFail();
|
||||
|
||||
try {
|
||||
$payment = $this->go_cardless->gateway->payments()->create([
|
||||
'params' => [
|
||||
'amount' => $request->amount,
|
||||
'currency' => $request->currency,
|
||||
'metadata' => [
|
||||
'payment_hash' => $this->go_cardless->payment_hash->hash,
|
||||
],
|
||||
'links' => [
|
||||
'mandate' => $token->token,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
if ($payment->status === 'pending_submission') {
|
||||
return $this->processPendingPayment($payment, ['token' => $token->hashed_id]);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment($payment);
|
||||
} catch (\Exception $exception) {
|
||||
throw new PaymentFailed($exception->getMessage(), $exception->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle pending payments for Direct Debit.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @param array $data
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processPendingPayment(\GoCardlessPro\Resources\Payment $payment, array $data = [])
|
||||
{
|
||||
$data = [
|
||||
'payment_method' => $data['token'],
|
||||
'payment_type' => PaymentType::SEPA,
|
||||
'amount' => $this->go_cardless->payment_hash->data->amount_with_fee,
|
||||
'transaction_reference' => $payment->id,
|
||||
'gateway_type_id' => GatewayType::SEPA,
|
||||
];
|
||||
|
||||
$payment = $this->go_cardless->createPayment($data, Payment::STATUS_PENDING);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_GOCARDLESS,
|
||||
$this->go_cardless->client,
|
||||
$this->go_cardless->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->go_cardless->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process unsuccessful payments for Direct Debit.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @return never
|
||||
*/
|
||||
public function processUnsuccessfulPayment(\GoCardlessPro\Resources\Payment $payment)
|
||||
{
|
||||
$this->go_cardless->sendFailureMail(
|
||||
$payment->status
|
||||
);
|
||||
|
||||
$message = [
|
||||
'server_response' => $payment,
|
||||
'data' => $this->go_cardless->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_GOCARDLESS,
|
||||
$this->go_cardless->client,
|
||||
$this->go_cardless->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed('Failed to process the payment.', 500);
|
||||
}
|
||||
}
|
@ -37,6 +37,8 @@ class GoCardlessPaymentDriver extends BaseDriver
|
||||
|
||||
public static $methods = [
|
||||
GatewayType::BANK_TRANSFER => \App\PaymentDrivers\GoCardless\ACH::class,
|
||||
GatewayType::DIRECT_DEBIT => \App\PaymentDrivers\GoCardless\DirectDebit::class,
|
||||
GatewayType::SEPA => \App\PaymentDrivers\GoCardless\SEPA::class,
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_GOCARDLESS;
|
||||
@ -62,6 +64,18 @@ class GoCardlessPaymentDriver extends BaseDriver
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
}
|
||||
|
||||
if (
|
||||
$this->client
|
||||
&& isset($this->client->country)
|
||||
&& in_array($this->client->country->iso_3166_3, ['GBR'])
|
||||
) {
|
||||
$types[] = GatewayType::DIRECT_DEBIT;
|
||||
}
|
||||
|
||||
if ($this->client->currency()->code === 'EUR') {
|
||||
$types[] = GatewayType::SEPA;
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
|
@ -73,24 +73,25 @@ class ActivityRepository extends BaseRepository
|
||||
if ($entity instanceof User || $entity->company->is_disabled)
|
||||
return;
|
||||
|
||||
|
||||
$backup = new Backup();
|
||||
|
||||
if (get_class($entity) == Invoice::class
|
||||
|| get_class($entity) == Quote::class
|
||||
|| get_class($entity) == Credit::class
|
||||
|| get_class($entity) == RecurringInvoice::class
|
||||
) {
|
||||
|
||||
|
||||
$backup = new Backup();
|
||||
$entity->load('client');
|
||||
$contact = $entity->client->primary_contact()->first();
|
||||
$backup->html_backup = $this->generateHtml($entity);
|
||||
$backup->amount = $entity->amount;
|
||||
$backup->activity_id = $activity->id;
|
||||
$backup->json_backup = '';
|
||||
$backup->save();
|
||||
|
||||
$backup->storeRemotely($this->generateHtml($entity), $entity->client);
|
||||
}
|
||||
|
||||
$backup->activity_id = $activity->id;
|
||||
$backup->json_backup = '';
|
||||
$backup->save();
|
||||
|
||||
}
|
||||
|
||||
public function getTokenId(array $event_vars)
|
||||
@ -126,7 +127,7 @@ class ActivityRepository extends BaseRepository
|
||||
|
||||
if(!$entity->invitations()->exists() || !$design){
|
||||
nlog("No invitations for entity {$entity->id} - {$entity->number}");
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
$entity->load('client.company', 'invitations');
|
||||
|
@ -483,6 +483,30 @@ class InvoiceService
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
//if paid invoice is attached to a recurring invoice - check if we need to unpause the recurring invoice
|
||||
|
||||
if ($this->invoice->status_id == Invoice::STATUS_PAID &&
|
||||
$this->invoice->recurring_id &&
|
||||
$this->invoice->company->pause_recurring_until_paid &&
|
||||
($this->invoice->recurring_invoice->status_id != RecurringInvoice::STATUS_ACTIVE || $this->invoice->recurring_invoice->status_id != RecurringInvoice::STATUS_COMPLETED))
|
||||
{
|
||||
$recurring_invoice = $this->invoice->recurring_invoice;
|
||||
|
||||
// Check next_send_date if it is in the past - calculate
|
||||
$next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay();
|
||||
|
||||
if(next_send_date->lt(now())){
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextDateByFrequency(now()->format('Y-m-d'));
|
||||
$recurring_invoice->save();
|
||||
}
|
||||
|
||||
// Start the recurring invoice
|
||||
$recurring_invoice->service()
|
||||
->start();
|
||||
|
||||
}
|
||||
*/
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -82,6 +82,9 @@ class MarkPaid extends AbstractService
|
||||
->updateBalance($payment->amount * -1)
|
||||
->updatePaidToDate($payment->amount)
|
||||
->setStatus(Invoice::STATUS_PAID)
|
||||
->save();
|
||||
|
||||
$this->invoice->service()
|
||||
->applyNumber()
|
||||
->deletePdf()
|
||||
->save();
|
||||
@ -103,7 +106,10 @@ class MarkPaid extends AbstractService
|
||||
->updatePaidToDate($payment->amount)
|
||||
->save();
|
||||
|
||||
$this->invoice->service()->workFlow()->save();
|
||||
$this->invoice
|
||||
->service()
|
||||
->workFlow()
|
||||
->save();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
@ -84,9 +84,19 @@ class SystemHealth
|
||||
'jobs_pending' => (int) Queue::size(),
|
||||
'pdf_engine' => (string) self::getPdfEngine(),
|
||||
'queue' => (string) config('queue.default'),
|
||||
'trailing_slash' => (bool) self::checkUrlState(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function checkUrlState()
|
||||
{
|
||||
if (env('APP_URL') && substr(env('APP_URL'), -1) == '/')
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public static function getPdfEngine()
|
||||
{
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja')
|
||||
|
@ -13,7 +13,7 @@
|
||||
"tasks",
|
||||
"freelancer"
|
||||
],
|
||||
"license": "Attribution Assurance License",
|
||||
"license": "Elastic License",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Hillel Coren",
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddDirectDebitToPaymentTypes extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('payment_types', function (Blueprint $table) {
|
||||
$type = new PaymentType();
|
||||
|
||||
$type->id = 42;
|
||||
$type->name = 'Direct Debit';
|
||||
$type->gateway_type_id = GatewayType::DIRECT_DEBIT;
|
||||
|
||||
$type->save();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use App\Models\GatewayType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddGatewayTypeForDirectDebit extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$type = new GatewayType();
|
||||
|
||||
$type->id = 18;
|
||||
$type->alias = 'direct_debit';
|
||||
$type->name = 'Direct Debit';
|
||||
|
||||
$type->save();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddFilenameToBackupsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('backups', function (Blueprint $table) {
|
||||
$table->text('filename')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
}
|
||||
}
|
6
public/flutter_service_worker.js
vendored
6
public/flutter_service_worker.js
vendored
@ -3,7 +3,7 @@ const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"version.json": "a6d23c92fdbd05308abe92bd317d56eb",
|
||||
"version.json": "27abc97e9c76cf112b697fa080c304b5",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1",
|
||||
@ -32,9 +32,9 @@ const RESOURCES = {
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"main.dart.js": "de3a4a0a08d96f1bc8db5ab84333c49f",
|
||||
"main.dart.js": "4b982138f175597df211146084e39b66",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||
"/": "3f4e5212b63fa2b34367df9b913aaebb"
|
||||
"/": "3debb9cbb687369f006a776cc7ab525b"
|
||||
};
|
||||
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
|
19048
public/main.dart.js
vendored
19048
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
19030
public/main.foss.dart.js
vendored
19030
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
18550
public/main.html.dart.js
vendored
18550
public/main.html.dart.js
vendored
File diff suppressed because one or more lines are too long
354740
public/main.next.dart.js
vendored
354740
public/main.next.dart.js
vendored
File diff suppressed because one or more lines are too long
519
public/main.profile.dart.js
vendored
519
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.61","build_number":"61"}
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.62","build_number":"62"}
|
@ -88,12 +88,8 @@ class SquareCreditCard {
|
||||
}
|
||||
catch(typeError){
|
||||
console.log(typeError);
|
||||
die("failed in the catch");
|
||||
}
|
||||
// console.log(" verification tokem = " + verificationToken.token);
|
||||
|
||||
// verificationToken = verificationResults.token;
|
||||
|
||||
console.debug('Verification Token:', verificationToken);
|
||||
|
||||
document.querySelector('input[name="verificationToken"]').value =
|
||||
|
@ -4330,6 +4330,7 @@ $LANG = array(
|
||||
'becs' => 'BECS Direct Debit',
|
||||
'becs_mandate' => 'By providing your bank account details, you agree to this <a href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request and the Direct Debit Request service agreement</a>, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of :company (the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above.',
|
||||
'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.',
|
||||
'direct_debit' => 'Direct Debit',
|
||||
'clone_to_expense' => 'Clone to expense',
|
||||
'checkout' => 'Checkout',
|
||||
'acss' => 'Pre-authorized debit payments',
|
||||
|
@ -36,6 +36,7 @@
|
||||
class="input w-full"
|
||||
type="email"
|
||||
name="{{ $field['key'] }}"
|
||||
value="{{ old($field['key']) }}"
|
||||
{{ $field['required'] ? 'required' : '' }} />
|
||||
@elseif($field['key'] === 'password')
|
||||
<input
|
||||
@ -63,6 +64,7 @@
|
||||
id="{{ $field['key'] }}"
|
||||
class="input w-full"
|
||||
name="{{ $field['key'] }}"
|
||||
value="{{ old($field['key']) }}"
|
||||
{{ $field['required'] ? 'required' : '' }} />
|
||||
@endif
|
||||
|
||||
|
@ -39,7 +39,7 @@
|
||||
data-token="{{ $token->token }}"
|
||||
name="payment-type"
|
||||
class="form-radio cursor-pointer toggle-payment-with-token"/>
|
||||
<span class="ml-1 cursor-pointer">{{ $token->meta->email }}</span>
|
||||
<span class="ml-1 cursor-pointer">{{ optional($token->meta)->email }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
@endisset
|
||||
|
@ -0,0 +1,56 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Direct Debit', 'card_title' => 'Direct Debit'])
|
||||
|
||||
@section('gateway_content')
|
||||
@if (count($tokens) > 0)
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
|
||||
@csrf
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
|
||||
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
|
||||
<input type="hidden" name="source" value="">
|
||||
<input type="hidden" name="amount" value="{{ $amount }}">
|
||||
<input type="hidden" name="currency" value="{{ $currency }}">
|
||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||
</form>
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||
@if (count($tokens) > 0)
|
||||
@foreach ($tokens as $token)
|
||||
<label class="mr-4">
|
||||
<input type="radio" data-token="{{ $token->hashed_id }}" name="payment-type"
|
||||
class="form-radio cursor-pointer toggle-payment-with-token" />
|
||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.payment_type_direct_debit') }}
|
||||
(#{{ $token->hashed_id }})</span>
|
||||
</label>
|
||||
@endforeach
|
||||
@endisset
|
||||
@endcomponent
|
||||
|
||||
@else
|
||||
@component('portal.ninja2020.components.general.card-element-single', ['title' => 'Direct Debit', 'show_title' => false])
|
||||
<span>{{ ctrans('texts.bank_account_not_linked') }}</span>
|
||||
|
||||
<a class="button button-link text-primary"
|
||||
href="{{ route('client.payment_methods.index') }}">{{ ctrans('texts.add_payment_method') }}</a>
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.pay_now')
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
<script>
|
||||
Array
|
||||
.from(document.getElementsByClassName('toggle-payment-with-token'))
|
||||
.forEach((element) => element.addEventListener('click', (element) => {
|
||||
document.querySelector('input[name=source]').value = element.target.dataset.token;
|
||||
}));
|
||||
|
||||
document.getElementById('pay-now').addEventListener('click', function() {
|
||||
document.getElementById('server-response').submit();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
@ -0,0 +1,56 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_SEPA'), 'card_title' => ctrans('texts.payment_type_SEPA')])
|
||||
|
||||
@section('gateway_content')
|
||||
@if (count($tokens) > 0)
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
|
||||
@csrf
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
|
||||
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
|
||||
<input type="hidden" name="source" value="">
|
||||
<input type="hidden" name="amount" value="{{ $amount }}">
|
||||
<input type="hidden" name="currency" value="{{ $currency }}">
|
||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||
</form>
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||
@if (count($tokens) > 0)
|
||||
@foreach ($tokens as $token)
|
||||
<label class="mr-4">
|
||||
<input type="radio" data-token="{{ $token->hashed_id }}" name="payment-type"
|
||||
class="form-radio cursor-pointer toggle-payment-with-token" />
|
||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.payment_type_SEPA') }}
|
||||
(#{{ $token->hashed_id }})</span>
|
||||
</label>
|
||||
@endforeach
|
||||
@endisset
|
||||
@endcomponent
|
||||
|
||||
@else
|
||||
@component('portal.ninja2020.components.general.card-element-single', ['title' => ctrans('texts.payment_type_SEPA'), 'show_title' => false])
|
||||
<span>{{ ctrans('texts.bank_account_not_linked') }}</span>
|
||||
|
||||
<a class="button button-link text-primary"
|
||||
href="{{ route('client.payment_methods.index') }}">{{ ctrans('texts.add_payment_method') }}</a>
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.pay_now')
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
<script>
|
||||
Array
|
||||
.from(document.getElementsByClassName('toggle-payment-with-token'))
|
||||
.forEach((element) => element.addEventListener('click', (element) => {
|
||||
document.querySelector('input[name=source]').value = element.target.dataset.token;
|
||||
}));
|
||||
|
||||
document.getElementById('pay-now').addEventListener('click', function() {
|
||||
document.getElementById('server-response').submit();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
@ -86,6 +86,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
|
||||
Route::resource('group_settings', 'GroupSettingController');
|
||||
Route::post('group_settings/bulk', 'GroupSettingController@bulk');
|
||||
Route::put('group_settings/{group_setting}/upload', 'GroupSettingController@upload')->name('group_settings.upload');
|
||||
|
||||
Route::post('import', 'ImportController@import')->name('import.import');
|
||||
Route::post('import_json', 'ImportJsonController@import')->name('import.import_json');
|
||||
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser\ClientPortal\Gateways\GoCardless;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Pages\ClientPortal\Login;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class DirectDebitTest extends DuskTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
foreach (static::$browsers as $browser) {
|
||||
$browser->driver->manage()->deleteAllCookies();
|
||||
}
|
||||
|
||||
$this->disableCompanyGateways();
|
||||
|
||||
CompanyGateway::where('gateway_key', 'b9886f9257f0c6ee7c302f1c74475f6c')->restore();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visit(new Login())
|
||||
->auth();
|
||||
});
|
||||
}
|
||||
|
||||
public function testPayingWithNoPreauthorizedIsntPossible()
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Direct Debit')
|
||||
->assertSee('To pay with a bank account, first you have to add it as payment method.');
|
||||
});
|
||||
}
|
||||
}
|
42
tests/Browser/ClientPortal/Gateways/GoCardless/SEPATest.php
Normal file
42
tests/Browser/ClientPortal/Gateways/GoCardless/SEPATest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser\ClientPortal\Gateways\GoCardless;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Pages\ClientPortal\Login;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class SEPATest extends DuskTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
foreach (static::$browsers as $browser) {
|
||||
$browser->driver->manage()->deleteAllCookies();
|
||||
}
|
||||
|
||||
$this->disableCompanyGateways();
|
||||
|
||||
CompanyGateway::where('gateway_key', 'b9886f9257f0c6ee7c302f1c74475f6c')->restore();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visit(new Login())
|
||||
->auth();
|
||||
});
|
||||
}
|
||||
|
||||
public function testPayingWithNoPreauthorizedIsntPossible()
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('SEPA Direct Debit')
|
||||
->assertSee('To pay with a bank account, first you have to add it as payment method.');
|
||||
});
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user