mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #7930 from turbo124/v5-develop
Fixes for SEPA auto billing
This commit is contained in:
commit
be27f2cbcc
@ -45,6 +45,8 @@ class InvoiceItem
|
||||
|
||||
public $gross_line_total = 0;
|
||||
|
||||
public $tax_amount = 0;
|
||||
|
||||
public $date = '';
|
||||
|
||||
public $custom_value1 = '';
|
||||
@ -75,6 +77,7 @@ class InvoiceItem
|
||||
'sort_id' => 'string',
|
||||
'line_total' => 'float',
|
||||
'gross_line_total' => 'float',
|
||||
'tax_amount' => 'float',
|
||||
'date' => 'string',
|
||||
'custom_value1' => 'string',
|
||||
'custom_value2' => 'string',
|
||||
|
@ -33,10 +33,11 @@ class QuoteFilters extends QueryFilters
|
||||
}
|
||||
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('quotes.custom_value1', 'like', '%'.$filter.'%')
|
||||
->orWhere('quotes.custom_value2', 'like', '%'.$filter.'%')
|
||||
->orWhere('quotes.custom_value3', 'like', '%'.$filter.'%')
|
||||
->orWhere('quotes.custom_value4', 'like', '%'.$filter.'%');
|
||||
$query->where('quotes.number', 'like', '%'.$filter.'%')
|
||||
->orwhere('quotes.custom_value1', 'like', '%'.$filter.'%')
|
||||
->orWhere('quotes.custom_value2', 'like', '%'.$filter.'%')
|
||||
->orWhere('quotes.custom_value3', 'like', '%'.$filter.'%')
|
||||
->orWhere('quotes.custom_value4', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,8 @@ class InvoiceItemSum
|
||||
|
||||
private $gross_line_total;
|
||||
|
||||
private $tax_amount;
|
||||
|
||||
private $currency;
|
||||
|
||||
private $total_taxes;
|
||||
@ -111,14 +113,10 @@ class InvoiceItemSum
|
||||
$this->setLineTotal($this->getLineTotal() - $this->formatValue($this->item->discount, $this->currency->precision));
|
||||
} else {
|
||||
|
||||
/*Test 16-08-2021*/
|
||||
$discount = ($this->item->line_total * ($this->item->discount / 100));
|
||||
|
||||
$this->setLineTotal($this->formatValue(($this->getLineTotal() - $discount), $this->currency->precision));
|
||||
/*Test 16-08-2021*/
|
||||
|
||||
//replaces the following
|
||||
|
||||
// $this->setLineTotal($this->getLineTotal() - $this->formatValue(round($this->item->line_total * ($this->item->discount / 100), 2), $this->currency->precision));
|
||||
}
|
||||
|
||||
$this->item->is_amount_discount = $this->invoice->is_amount_discount;
|
||||
@ -160,6 +158,8 @@ class InvoiceItemSum
|
||||
|
||||
$this->item->gross_line_total = $this->getLineTotal() + $item_tax;
|
||||
|
||||
$this->item->tax_amount = $item_tax;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,8 @@ class InvoiceItemSumInclusive
|
||||
|
||||
private $tax_collection;
|
||||
|
||||
private $tax_amount;
|
||||
|
||||
public function __construct($invoice)
|
||||
{
|
||||
$this->tax_collection = collect([]);
|
||||
@ -144,6 +146,8 @@ class InvoiceItemSumInclusive
|
||||
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
|
||||
}
|
||||
|
||||
$this->item->tax_amount = $this->formatValue($item_tax, $this->currency->precision);
|
||||
|
||||
$this->setTotalTaxes($this->formatValue($item_tax, $this->currency->precision));
|
||||
|
||||
return $this;
|
||||
|
@ -27,7 +27,7 @@ use App\Services\Bank\BankService;
|
||||
use App\Transformers\BankIntegrationTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class BankIntegrationController extends BaseController
|
||||
{
|
||||
@ -572,12 +572,17 @@ class BankIntegrationController extends BaseController
|
||||
|
||||
$account = auth()->user()->account;
|
||||
|
||||
if(Cache::get("throttle_polling:{$account->key}"))
|
||||
return response()->json(BankIntegration::query()->company(), 200);
|
||||
|
||||
$account->bank_integrations->each(function ($bank_integration) use ($account){
|
||||
|
||||
ProcessBankTransactions::dispatch($account->bank_integration_account_id, $bank_integration);
|
||||
|
||||
});
|
||||
|
||||
Cache::put("throttle_polling:{$account->key}", true, 300);
|
||||
|
||||
return response()->json(BankIntegration::query()->company(), 200);
|
||||
}
|
||||
|
||||
|
@ -56,8 +56,6 @@ class InvoiceController extends Controller
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
// $invoice->service()->removeUnpaidGatewayFees()->save();
|
||||
|
||||
$invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
|
||||
|
||||
if ($invitation && auth()->guard('contact') && ! session()->get('is_silent') && ! $invitation->viewed_date) {
|
||||
|
@ -94,16 +94,13 @@ class TwilioController extends BaseController
|
||||
|
||||
if($verification_check->status == 'approved'){
|
||||
|
||||
if($request->query('validate_only') == 'true')
|
||||
return response()->json(['message' => 'SMS verified'], 200);
|
||||
|
||||
|
||||
$account->account_sms_verified = true;
|
||||
$account->save();
|
||||
|
||||
//on confirmation we set the users phone number.
|
||||
$user = auth()->user();
|
||||
$user->phone = $account->account_sms_verification_number;
|
||||
$user->verified_phone_number = true;
|
||||
$user->save();
|
||||
|
||||
return response()->json(['message' => 'SMS verified'], 200);
|
||||
@ -126,7 +123,6 @@ class TwilioController extends BaseController
|
||||
|
||||
$twilio = new Client($sid, $token);
|
||||
|
||||
|
||||
try {
|
||||
$verification = $twilio->verify
|
||||
->v2
|
||||
@ -167,9 +163,11 @@ class TwilioController extends BaseController
|
||||
"code" => $request->code
|
||||
]);
|
||||
|
||||
|
||||
if($verification_check->status == 'approved'){
|
||||
|
||||
if($request->query('validate_only') == 'true')
|
||||
return response()->json(['message' => 'SMS verified'], 200);
|
||||
|
||||
$user->google_2fa_secret = '';
|
||||
$user->sms_verification_code = '';
|
||||
$user->save();
|
||||
|
@ -26,7 +26,7 @@ class UpdateAccountRequest extends Request
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return (auth()->user()->isAdmin() || auth()->user()->isOwner()) && (int) $this->account->id === auth()->user()->account_id;
|
||||
return (auth()->user()->isAdmin() || auth()->user()->isOwner()) && ($this->account->id == auth()->user()->account_id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,7 +33,6 @@ class UpdateBankTransactionRequest extends Request
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
$rules = [
|
||||
'date' => 'bail|required|date',
|
||||
'description' => 'bail|sometimes|string',
|
||||
'amount' => 'numeric|required',
|
||||
];
|
||||
|
||||
|
@ -65,8 +65,9 @@ class UpdateUserRequest extends Request
|
||||
$input['last_name'] = strip_tags($input['last_name']);
|
||||
}
|
||||
|
||||
if(array_key_exists('phone', $input) && isset($input['phone']) && strlen($input['phone']) > 1 && ($this->user->phone != $input['phone']))
|
||||
if(array_key_exists('phone', $input) && isset($input['phone']) && strlen($input['phone']) > 1 && ($this->user->phone != $input['phone'])){
|
||||
$this->phone_has_changed = true;
|
||||
}
|
||||
|
||||
if(array_key_exists('oauth_provider_id', $input) && $input['oauth_provider_id'] == '')
|
||||
$input['oauth_user_id'] = '';
|
||||
|
@ -68,7 +68,7 @@ class HasValidPhoneNumber implements Rule
|
||||
|
||||
request()->merge(['validated_phone' => $phone_number->phoneNumber ]);
|
||||
|
||||
$user->verified_phone_number = true;
|
||||
$user->verified_phone_number = false;
|
||||
$user->save();
|
||||
|
||||
return true;
|
||||
|
@ -56,10 +56,10 @@ class BankTransformer extends BaseTransformer
|
||||
private function calculateType($transaction)
|
||||
{
|
||||
|
||||
if(array_key_exists('bank.base_type', $transaction) && $transaction['bank.base_type'] == 'CREDIT')
|
||||
if(array_key_exists('bank.base_type', $transaction) && ($transaction['bank.base_type'] == 'CREDIT') || strtolower($transaction['bank.base_type']) == 'deposit')
|
||||
return 'CREDIT';
|
||||
|
||||
if(array_key_exists('bank.base_type', $transaction) && $transaction['bank.base_type'] == 'DEBIT')
|
||||
if(array_key_exists('bank.base_type', $transaction) && ($transaction['bank.base_type'] == 'DEBIT') || strtolower($transaction['bank.bank_type']) == 'withdrawal')
|
||||
return 'DEBIT';
|
||||
|
||||
if(array_key_exists('bank.category_id', $transaction))
|
||||
|
@ -92,11 +92,14 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
$this->company = Company::find($this->company_id);
|
||||
|
||||
$yodlee = new Yodlee($this->company->account->bank_integration_account_id);
|
||||
if($this->company->account->bank_integration_account_id)
|
||||
$yodlee = new Yodlee($this->company->account->bank_integration_account_id);
|
||||
else
|
||||
$yodlee = false;
|
||||
|
||||
$bank_categories = Cache::get('bank_categories');
|
||||
|
||||
if(!$bank_categories){
|
||||
if(!$bank_categories && $yodlee){
|
||||
$_categories = $yodlee->getTransactionCategories();
|
||||
$this->categories = collect($_categories->transactionCategory);
|
||||
Cache::forever('bank_categories', $this->categories);
|
||||
@ -159,7 +162,7 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
$_invoices = Invoice::withTrashed()->find($this->getInvoices($input['invoice_ids']));
|
||||
|
||||
$amount = $this->bt->amount;
|
||||
$amount = $this->bt->amount;
|
||||
|
||||
if($_invoices && $this->checkPayable($_invoices)){
|
||||
|
||||
@ -220,7 +223,7 @@ class MatchBankTransactions implements ShouldQueue
|
||||
$this->applied_amount += $this->invoice->balance;
|
||||
$this->available_balance = $this->available_balance - $this->invoice->balance;
|
||||
}
|
||||
elseif(floatval($this->invoice->balance) > floatval($this->available_balance) && $this->available_balance > 0)
|
||||
elseif(floatval($this->invoice->balance) >= floatval($this->available_balance) && $this->available_balance > 0)
|
||||
{
|
||||
$_amount = $this->available_balance;
|
||||
$this->applied_amount += $this->available_balance;
|
||||
|
@ -83,9 +83,6 @@ class CSVIngest implements ShouldQueue
|
||||
|
||||
$this->checkContacts();
|
||||
|
||||
if(Ninja::isHosted())
|
||||
app('queue.worker')->shouldQuit = 1;
|
||||
|
||||
}
|
||||
|
||||
private function checkContacts()
|
||||
|
@ -60,14 +60,14 @@ class BankTransactionSync implements ShouldQueue
|
||||
|
||||
public function syncTransactions()
|
||||
{
|
||||
$a = Account::with('bank_integrations')->where('auto_sync', true)->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account){
|
||||
$a = Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account){
|
||||
|
||||
// $queue = Ninja::isHosted() ? 'bank' : 'default';
|
||||
|
||||
if($account->isPaid() && $account->plan == 'enterprise')
|
||||
{
|
||||
|
||||
$account->bank_integrations->each(function ($bank_integration) use ($account){
|
||||
$account->bank_integrations()->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account){
|
||||
|
||||
ProcessBankTransactions::dispatchSync($account->bank_integration_account_id, $bank_integration);
|
||||
|
||||
|
@ -257,6 +257,12 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
|
||||
$invoice_field = $invoice->{$field};
|
||||
|
||||
if(in_array($field, ['amount', 'balance']))
|
||||
$invoice_field = Number::formatMoney($invoice_field, $this->client);
|
||||
|
||||
if($field == 'due_date')
|
||||
$invoice_field = $this->translateDate($invoice_field, $this->client->date_format(), $this->client->locale());
|
||||
|
||||
$invoicex .= ctrans('texts.invoice_number_short') . "{$invoice->number} {$invoice_field}";
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
@ -95,4 +96,22 @@ class BankTransaction extends BaseModel
|
||||
return $this->belongsTo(Account::class)->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
public function matchInvoiceNumber()
|
||||
{
|
||||
|
||||
if(strlen($this->description) > 1)
|
||||
{
|
||||
|
||||
$i = Invoice::where('company_id', $this->company_id)
|
||||
->whereIn('status_id', [1,2,3])
|
||||
->where('is_deleted', 0)
|
||||
->where('number', 'LIKE', '%'.$this->description.'%')
|
||||
->first();
|
||||
|
||||
return $i ?: false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -89,18 +89,23 @@ class CreditCard
|
||||
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->firstOrFail();
|
||||
$payment_hash = PaymentHash::where('hash', $request->input('payment_hash'))->firstOrFail();
|
||||
$amount_with_fee = $payment_hash->data->total->amount_with_fee;
|
||||
$invoice_totals = $payment_hash->data->total->invoice_totals;
|
||||
$fee_total = 0;
|
||||
|
||||
for ($i = ($invoice_totals * 100) ; $i < ($amount_with_fee * 100); $i++) {
|
||||
$calculated_fee = ( 3 * $i) / 100;
|
||||
$calculated_amount_with_fee = round(($i + $calculated_fee) / 100,2);
|
||||
if ($calculated_amount_with_fee == $amount_with_fee) {
|
||||
$fee_total = round($calculated_fee / 100,2);
|
||||
$amount_with_fee = $calculated_amount_with_fee;
|
||||
break;
|
||||
$fees_and_limits = $this->forte->company_gateway->getFeesAndLimits(GatewayType::CREDIT_CARD);
|
||||
|
||||
if(property_exists($fees_and_limits, 'fee_percent') && $fees_and_limits->fee_percent > 0)
|
||||
{
|
||||
for ($i = ($invoice_totals * 100) ; $i < ($amount_with_fee * 100); $i++) {
|
||||
$calculated_fee = ( 3 * $i) / 100;
|
||||
$calculated_amount_with_fee = round(($i + $calculated_fee) / 100,2);
|
||||
if ($calculated_amount_with_fee == $amount_with_fee) {
|
||||
$fee_total = round($calculated_fee / 100,2);
|
||||
$amount_with_fee = $calculated_amount_with_fee;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,11 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
];
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Omnipay PayPal_Express gateway.
|
||||
*
|
||||
|
@ -106,15 +106,6 @@ class SquarePaymentDriver extends BaseDriver
|
||||
/** @var ApiResponse */
|
||||
$response = $this->square->getRefundsApi()->refund($body);
|
||||
|
||||
// if ($response->isSuccess()) {
|
||||
// return [
|
||||
// 'transaction_reference' => $refund->action_id,
|
||||
// 'transaction_response' => json_encode($response),
|
||||
// 'success' => $checkout_payment->status == 'Refunded',
|
||||
// 'description' => $checkout_payment->status,
|
||||
// 'code' => $checkout_payment->http_code,
|
||||
// ];
|
||||
// }
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
|
@ -137,9 +137,6 @@ class Charge
|
||||
return false;
|
||||
}
|
||||
|
||||
if($response?->status != 'succeeded')
|
||||
$this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400));
|
||||
|
||||
if ($cgt->gateway_type_id == GatewayType::SEPA) {
|
||||
$payment_method_type = PaymentType::SEPA;
|
||||
$status = Payment::STATUS_PENDING;
|
||||
@ -148,6 +145,12 @@ class Charge
|
||||
$status = Payment::STATUS_COMPLETED;
|
||||
}
|
||||
|
||||
if($response?->status == 'processing'){
|
||||
//allows us to jump over the next stage - used for SEPA
|
||||
}elseif($response?->status != 'succeeded'){
|
||||
$this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'gateway_type_id' => $cgt->gateway_type_id,
|
||||
'payment_type' => $this->transformPaymentTypeToConstant($payment_method_type),
|
||||
|
@ -31,6 +31,13 @@ class BankTransactionRepository extends BaseRepository
|
||||
|
||||
$bank_transaction->save();
|
||||
|
||||
if($bank_transaction->base_type == 'CREDIT' && $invoice = $bank_transaction->matchInvoiceNumber())
|
||||
{
|
||||
$bank_transaction->invoice_ids = $invoice->hashed_id;
|
||||
$bank_transaction->status_id = BankTransaction::STATUS_MATCHED;
|
||||
$bank_transaction->save();
|
||||
}
|
||||
|
||||
return $bank_transaction;
|
||||
}
|
||||
|
||||
|
@ -485,6 +485,7 @@ class HtmlEngine
|
||||
$data['$product.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
|
||||
$data['$product.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.gross_line_total')];
|
||||
$data['$product.tax_amount'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.description'] = ['value' => '', 'label' => ctrans('texts.description')];
|
||||
$data['$product.unit_cost'] = ['value' => '', 'label' => ctrans('texts.unit_cost')];
|
||||
$data['$product.product1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product1')];
|
||||
|
@ -161,8 +161,6 @@ trait MakesInvoiceValues
|
||||
|
||||
if (strlen($user_columns) > 1) {
|
||||
foreach ($items as $key => $item) {
|
||||
// $tmp = str_replace(array_keys($data), array_values($data), $user_columns);
|
||||
// $tmp = str_replace(array_keys($item), array_values($item), $tmp);
|
||||
$tmp = strtr($user_columns, $data);
|
||||
$tmp = strtr($tmp, $item);
|
||||
|
||||
@ -178,8 +176,6 @@ trait MakesInvoiceValues
|
||||
$table_row .= '</tr>';
|
||||
|
||||
foreach ($items as $key => $item) {
|
||||
// $tmp = str_replace(array_keys($item), array_values($item), $table_row);
|
||||
// $tmp = str_replace(array_keys($data), array_values($data), $tmp);
|
||||
$tmp = strtr($table_row, $item);
|
||||
$tmp = strtr($tmp, $data);
|
||||
|
||||
@ -210,11 +206,13 @@ trait MakesInvoiceValues
|
||||
'tax_name1',
|
||||
'tax_name2',
|
||||
'tax_name3',
|
||||
'tax_amount',
|
||||
],
|
||||
[
|
||||
'tax',
|
||||
'tax',
|
||||
'tax',
|
||||
'tax',
|
||||
],
|
||||
$columns
|
||||
);
|
||||
@ -329,6 +327,12 @@ trait MakesInvoiceValues
|
||||
$data[$key][$table_type.'.gross_line_total'] = '';
|
||||
}
|
||||
|
||||
if (property_exists($item, 'tax_amount')) {
|
||||
$data[$key][$table_type.'.tax_amount'] = ($item->tax_amount == 0) ? '' : Number::formatMoney($item->tax_amount, $entity);
|
||||
} else {
|
||||
$data[$key][$table_type.'.tax_amount'] = '';
|
||||
}
|
||||
|
||||
if (isset($item->discount) && $item->discount > 0) {
|
||||
if ($item->is_amount_discount) {
|
||||
$data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $entity);
|
||||
|
@ -355,6 +355,7 @@ class VendorHtmlEngine
|
||||
$data['$product.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
|
||||
$data['$product.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.gross_line_total')];
|
||||
$data['$product.tax_amount'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.description'] = ['value' => '', 'label' => ctrans('texts.description')];
|
||||
$data['$product.unit_cost'] = ['value' => '', 'label' => ctrans('texts.unit_cost')];
|
||||
$data['$product.product1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product1')];
|
||||
|
@ -109,7 +109,7 @@ Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']],
|
||||
Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () {
|
||||
Route::put('accounts/{account}', [AccountController::class, 'update'])->name('account.update');
|
||||
Route::resource('bank_integrations', BankIntegrationController::class); // name = (clients. index / create / show / update / destroy / edit
|
||||
Route::post('bank_integrations/refresh_accounts', [BankIntegrationController::class, 'refreshAccounts'])->name('bank_integrations.refresh_accounts')->middleware('throttle:1,1');
|
||||
Route::post('bank_integrations/refresh_accounts', [BankIntegrationController::class, 'refreshAccounts'])->name('bank_integrations.refresh_accounts')->middleware('throttle:30,1');
|
||||
Route::post('bank_integrations/remove_account/{acc_id}', [BankIntegrationController::class, 'removeAccount'])->name('bank_integrations.remove_account');
|
||||
Route::post('bank_integrations/get_transactions/{acc_id}', [BankIntegrationController::class, 'getTransactions'])->name('bank_integrations.transactions')->middleware('throttle:1,1');
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user