Merge pull request #7930 from turbo124/v5-develop

Fixes for SEPA auto billing
This commit is contained in:
David Bomba 2022-11-10 22:01:46 +11:00 committed by GitHub
commit be27f2cbcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 109 additions and 58 deletions

View File

@ -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',

View File

@ -33,7 +33,8 @@ class QuoteFilters extends QueryFilters
}
return $this->builder->where(function ($query) use ($filter) {
$query->where('quotes.custom_value1', '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.'%');

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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) {

View File

@ -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();

View File

@ -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);
}
/**

View File

@ -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',
];

View File

@ -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'] = '';

View File

@ -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;

View File

@ -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))

View File

@ -92,11 +92,14 @@ class MatchBankTransactions implements ShouldQueue
$this->company = Company::find($this->company_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);
@ -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;

View File

@ -83,9 +83,6 @@ class CSVIngest implements ShouldQueue
$this->checkContacts();
if(Ninja::isHosted())
app('queue.worker')->shouldQuit = 1;
}
private function checkContacts()

View File

@ -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);

View File

@ -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}";
}

View File

@ -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;
}
}

View File

@ -89,11 +89,15 @@ 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;
$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);
@ -103,6 +107,7 @@ class CreditCard
break;
}
}
}
try {
$curl = curl_init();

View File

@ -43,6 +43,11 @@ class PayPalExpressPaymentDriver extends BaseDriver
];
}
public function init()
{
return $this;
}
/**
* Initialize Omnipay PayPal_Express gateway.
*

View File

@ -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)

View File

@ -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),

View File

@ -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;
}

View File

@ -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')];

View File

@ -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);

View File

@ -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')];

View File

@ -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');