1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-12 14:12:44 +01:00

Throttle payment methods to prevent spam:

This commit is contained in:
David Bomba 2022-05-11 15:25:33 +10:00
parent ffbfc11407
commit f604e463c2
9 changed files with 373 additions and 10 deletions

View File

@ -33,6 +33,7 @@ class PaymentFactory
$payment->transaction_reference = null;
$payment->payer_id = null;
$payment->status_id = Payment::STATUS_PENDING;
$payment->exchange_rate = 1;
return $payment;
}

View File

@ -30,6 +30,11 @@ class PaymentMethodController extends Controller
{
use MakesDates;
public function __construct()
{
$this->middleware('throttle:10,1')->only('store');
}
/**
* Display a listing of the resource.
*
@ -92,7 +97,6 @@ class PaymentMethodController extends Controller
public function verify(ClientGatewayToken $payment_method)
{
// $gateway = $this->getClientGateway();
return $payment_method->gateway
->driver(auth()->user()->client)

View File

@ -22,6 +22,7 @@ class BlackListRule implements Rule
private array $blacklist = [
'candassociates.com',
'vusra.com',
'fourthgenet.com',
];
/**

View File

@ -1450,6 +1450,20 @@ class CompanyImport implements ShouldQueue
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextRecurringExpenseNumber($new_obj);
}
elseif($class == 'App\Models\Project' && is_null($obj->{$match_key})){
$new_obj = new Project();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextProjectNumber($new_obj);
}
elseif($class == 'App\Models\Task' && is_null($obj->{$match_key})){
$new_obj = new Task();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextTaskNumber($new_obj);
}
elseif($class == 'App\Models\CompanyLedger'){
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],

View File

@ -0,0 +1,89 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Report;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Services\Report\ProfitLoss;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProfitAndLoss implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected Company $company;
protected array $payload;
/**
* Create a new job instance.
*
* @param RecurringInvoice $recurring_invoice
* @param string $db
*/
public function __construct(Company $company, array $payload)
{
$this->company = $company;
$this->payload = $payload;
}
/**
* Execute the job.
*
* @return void
*/
public function handle() : void
{
MultiDB::setDb($this->company->db);
/*
payload variables.
start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
}
public function failed($exception = null)
{
}
}

View File

@ -66,7 +66,11 @@ class PaymentRepository extends BaseRepository {
//check currencies here and fill the exchange rate data if necessary
if (! $payment->id) {
$this->processExchangeRates($data, $payment);
$payment = $this->processExchangeRates($data, $payment);
/* This is needed here otherwise the ->fill() overwrites anything that exists*/
if($payment->exchange_rate != 1)
unset($data['exchange_rate']);
$is_existing_payment = false;
$client = Client::where('id', $data['client_id'])->withTrashed()->first();
@ -100,7 +104,12 @@ class PaymentRepository extends BaseRepository {
$payment->status_id = Payment::STATUS_COMPLETED;
if (! $payment->currency_id && $client) {
$payment->currency_id = $client->company->settings->currency_id;
if(property_exists($client->settings, 'currency_id'))
$payment->currency_id = $client->settings->currency_id;
else
$payment->currency_id = $client->company->settings->currency_id;
}
$payment->save();
@ -199,8 +208,9 @@ class PaymentRepository extends BaseRepository {
public function processExchangeRates($data, $payment)
{
if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']))
if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']) && $data['exchange_rate'] != 1){
return $payment;
}
$client = Client::withTrashed()->find($data['client_id']);
@ -212,7 +222,6 @@ class PaymentRepository extends BaseRepository {
$exchange_rate = new CurrencyApi();
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
// $payment->exchange_currency_id = $client_currency;
$payment->exchange_currency_id = $company_currency;
$payment->currency_id = $client_currency;
@ -221,7 +230,6 @@ class PaymentRepository extends BaseRepository {
$payment->currency_id = $company_currency;
return $payment;
}

View File

@ -61,7 +61,7 @@ class MarkPaid extends AbstractService
$payment->transaction_reference = ctrans('texts.manual_entry');
$payment->currency_id = $this->invoice->client->getSetting('currency_id');
$payment->is_manual = true;
if($this->invoice->company->timezone())
$payment->date = now()->addSeconds($this->invoice->company->timezone()->utc_offset)->format('Y-m-d');
@ -149,7 +149,7 @@ class MarkPaid extends AbstractService
//$payment->exchange_currency_id = $client_currency; // 23/06/2021
$payment->exchange_currency_id = $company_currency;
$payment->save();
$payment->saveQuietly();
}

View File

@ -0,0 +1,246 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Report;
use App\Models\Company;
use Illuminate\Support\Carbon;
class ProfitLoss
{
private bool $is_income_billed = true;
private bool $is_expense_billed = true;
private bool $is_tax_included = true;
private $start_date;
private $end_date;
/*
payload variables.
start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
protected array $payload;
protected Company $company;
public function __construct(Company $company, array $payload)
{
$this->company = $company;
$this->payload = $payload;
$this->setBillingReportType();
}
public function build()
{
//get income
//sift foreign currencies - calculate both converted foreign amounts to native currency and also also group amounts by currency.
//get expenses
}
/*
//returns an array of objects
=> [
{#2047
+"amount": "706.480000",
+"total_taxes": "35.950000",
+"currency_id": ""1"",
+"net_converted_amount": "670.5300000000",
},
{#2444
+"amount": "200.000000",
+"total_taxes": "0.000000",
+"currency_id": ""23"",
+"net_converted_amount": "1.7129479802",
},
{#2654
+"amount": "140.000000",
+"total_taxes": "40.000000",
+"currency_id": ""12"",
+"net_converted_amount": "69.3275024282",
},
]
*/
private function invoiceIncome()
{
return \DB::select( \DB::raw("
SELECT
sum(invoices.amount) as amount,
sum(invoices.total_taxes) as total_taxes,
sum(invoices.amount - invoices.total_taxes) as net_amount,
IFNULL(JSON_EXTRACT( settings, '$.currency_id' ), :company_currency) AS currency_id,
(sum(invoices.amount - invoices.total_taxes) / IFNULL(invoices.exchange_rate, 1)) AS net_converted_amount
FROM clients
JOIN invoices
on invoices.client_id = clients.id
WHERE invoices.status_id IN (2,3,4)
AND invoices.company_id = :company_id
AND invoices.amount > 0
AND clients.is_deleted = 0
AND invoices.is_deleted = 0
AND (invoices.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
"), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date] );
//
// $total = array_reduce( commissionsArray, function ($sum, $entry) {
// $sum += $entry->commission;
// return $sum;
// }, 0);
}
private function paymentIncome()
{
return \DB::select( \DB::raw("
SELECT
SUM(coalesce(payments.amount - payments.refunded,0)) as payments,
SUM(coalesce(payments.amount - payments.refunded,0)) * IFNULL(payments.exchange_rate ,1) as payments_converted
FROM clients
INNER JOIN
payments ON
clients.id=payments.client_id
WHERE payments.status_id IN (1,4,5,6)
AND clients.is_deleted = false
AND payments.is_deleted = false
AND payments.company_id = :company_id
AND (payments.date BETWEEN :start_date AND :end_date)
GROUP BY payments.currency_id
ORDER BY payments.currency_id;
"), ['company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date]);
}
private function expenseCalc()
{
return \DB::select( \DB::raw("
SELECT sum(expenses.amount) as amount,
IFNULL(expenses.currency_id, :company_currency) as currency_id
FROM expenses
WHERE expenses.is_deleted = 0
AND expenses.company_id = :company_id
AND (expenses.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
"), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date] );
}
private function setBillingReportType()
{
if(array_key_exists('income_billed', $this->payload))
$this->is_income_billed = boolval($this->payload['income_billed']);
if(array_key_exists('expense_billed', $this->payload))
$this->is_expense_billed = boolval($this->payload['expense_billed']);
if(array_key_exists('include_tax', $this->payload))
$this->is_tax_included = boolval($this->payload['is_tax_included']);
return $this;
}
private function addDateRange($query)
{
$date_range = $this->payload['date_range'];
try{
$custom_start_date = Carbon::parse($this->payload['start_date']);
$custom_end_date = Carbon::parse($this->payload['end_date']);
}
catch(\Exception $e){
$custom_start_date = now()->startOfYear();
$custom_end_date = now();
}
switch ($date_range) {
case 'all':
$this->start_date = now()->subYears(50);
$this->end_date = now();
// return $query;
case 'last7':
$this->start_date = now()->subDays(7);
$this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->subDays(7), now()])->orderBy($this->date_key, 'ASC');
case 'last30':
$this->start_date = now()->subDays(30);
$this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->subDays(30), now()])->orderBy($this->date_key, 'ASC');
case 'this_month':
$this->start_date = now()->startOfMonth();
$this->end_date = now();
//return $query->whereBetween($this->date_key, [now()->startOfMonth(), now()])->orderBy($this->date_key, 'ASC');
case 'last_month':
$this->start_date = now()->startOfMonth()->subMonth();
$this->end_date = now()->startOfMonth()->subMonth()->endOfMonth();
//return $query->whereBetween($this->date_key, [now()->startOfMonth()->subMonth(), now()->startOfMonth()->subMonth()->endOfMonth()])->orderBy($this->date_key, 'ASC');
case 'this_quarter':
$this->start_date = (new \Carbon\Carbon('-3 months'))->firstOfQuarter();
$this->end_date = (new \Carbon\Carbon('-3 months'))->lastOfQuarter();
//return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-3 months'))->firstOfQuarter(), (new \Carbon\Carbon('-3 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC');
case 'last_quarter':
$this->start_date = (new \Carbon\Carbon('-6 months'))->firstOfQuarter();
$this->end_date = (new \Carbon\Carbon('-6 months'))->lastOfQuarter();
//return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-6 months'))->firstOfQuarter(), (new \Carbon\Carbon('-6 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC');
case 'this_year':
$this->start_date = now()->startOfYear();
$this->end_date = now();
//return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC');
case 'custom':
$this->start_date = $custom_start_date;
$this->end_date = $custom_end_date;
//return $query->whereBetween($this->date_key, [$custom_start_date, $custom_end_date])->orderBy($this->date_key, 'ASC');
default:
$this->start_date = now()->startOfYear();
$this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC');
}
}
}

View File

@ -8,7 +8,7 @@ Route::get('client/login', 'Auth\ContactLoginController@showLoginForm')->name('c
Route::post('client/login', 'Auth\ContactLoginController@login')->name('client.login.submit');
Route::get('client/register/{company_key?}', 'Auth\ContactRegisterController@showRegisterForm')->name('client.register')->middleware(['domain_db', 'contact_account', 'contact_register','locale']);
Route::post('client/register/{company_key?}', 'Auth\ContactRegisterController@register')->middleware(['domain_db', 'contact_account', 'contact_register', 'locale','throttle:10,1']);
Route::post('client/register/{company_key?}', 'Auth\ContactRegisterController@register')->middleware(['domain_db', 'contact_account', 'contact_register', 'locale', 'throttle:10,1']);
Route::get('client/password/reset', 'Auth\ContactForgotPasswordController@showLinkRequestForm')->name('client.password.request')->middleware(['domain_db', 'contact_account','locale']);
Route::post('client/password/email', 'Auth\ContactForgotPasswordController@sendResetLinkEmail')->name('client.password.email')->middleware('locale');
@ -62,7 +62,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db','check_clie
Route::put('profile/{client_contact}/localization', 'ClientPortal\ProfileController@updateClientLocalization')->name('profile.edit_localization');
Route::get('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@verify')->name('payment_methods.verification');
Route::post('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@processVerification');
Route::post('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@processVerification')->middleware(['throttle:10,1']);
Route::get('payment_methods/confirm', 'ClientPortal\PaymentMethodController@store')->name('payment_methods.confirm');