mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 20:22:42 +01:00
commit
b707671cd8
@ -1 +1 @@
|
||||
5.10.16
|
||||
5.10.17
|
@ -15,7 +15,6 @@ use App\Jobs\Cron\AutoBillCron;
|
||||
use App\Jobs\Cron\RecurringExpensesCron;
|
||||
use App\Jobs\Cron\RecurringInvoicesCron;
|
||||
use App\Jobs\Cron\SubscriptionCron;
|
||||
use App\Jobs\Cron\UpdateCalculatedFields;
|
||||
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
|
||||
use App\Jobs\Ninja\AdjustEmailQuota;
|
||||
use App\Jobs\Ninja\BankTransactionSync;
|
||||
@ -33,6 +32,7 @@ use App\Jobs\Util\SchedulerCheck;
|
||||
use App\Jobs\Util\UpdateExchangeRates;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\PaymentDrivers\Rotessa\Jobs\TransactionReport;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
@ -65,12 +65,12 @@ class Kernel extends ConsoleKernel
|
||||
/* Checks for scheduled tasks */
|
||||
$schedule->job(new TaskScheduler())->hourlyAt(10)->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
|
||||
|
||||
/* Checks Rotessa Transactions */
|
||||
$schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();
|
||||
|
||||
/* Stale Invoice Cleanup*/
|
||||
$schedule->job(new CleanStaleInvoiceOrder())->hourlyAt(30)->withoutOverlapping()->name('stale-invoice-job')->onOneServer();
|
||||
|
||||
/* Stale Invoice Cleanup*/
|
||||
$schedule->job(new UpdateCalculatedFields())->hourlyAt(40)->withoutOverlapping()->name('update-calculated-fields-job')->onOneServer();
|
||||
|
||||
/* Checks for large companies and marked them as is_large */
|
||||
$schedule->job(new CompanySizeCheck())->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();
|
||||
|
||||
|
@ -567,9 +567,9 @@ class CompanyGatewayController extends BaseController
|
||||
{
|
||||
|
||||
//Throttle here
|
||||
if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) {
|
||||
return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
|
||||
}
|
||||
// if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) {
|
||||
// return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
|
||||
// }
|
||||
|
||||
dispatch(function () use ($company_gateway) {
|
||||
MultiDB::setDb($company_gateway->company->db);
|
||||
|
@ -27,16 +27,13 @@ class AccountComponent extends Component
|
||||
'routing_number' => null,
|
||||
'institution_number' => null,
|
||||
'transit_number' => null,
|
||||
'bank_name' => ' ',
|
||||
'bank_name' => null,
|
||||
'account_number' => null,
|
||||
'country' => 'US',
|
||||
"authorization_type" => 'Online'
|
||||
];
|
||||
|
||||
public array $account;
|
||||
|
||||
public function __construct(array $account) {
|
||||
$this->account = $account;
|
||||
public function __construct(public array $account) {
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields) );
|
||||
}
|
||||
|
||||
|
@ -25,10 +25,7 @@ class AddressComponent extends Component
|
||||
'country' => 'US'
|
||||
];
|
||||
|
||||
public array $address;
|
||||
|
||||
public function __construct(array $address) {
|
||||
$this->address = $address;
|
||||
public function __construct(public array $address) {
|
||||
if(strlen($this->address['state']) > 2 ) {
|
||||
$this->address['state'] = $this->address['country'] == 'US' ? array_search($this->address['state'], USStates::$states) : CAProvinces::getAbbreviation($this->address['state']);
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ class ContactComponent extends Component
|
||||
|
||||
$contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
|
||||
'home_phone' =>$contact->client->phone,
|
||||
'custom_identifier' => $contact->client->number,
|
||||
'custom_identifier' => $contact->client->client_hash,
|
||||
'name' =>$contact->client->name,
|
||||
'id' => $contact->client->contact_key,
|
||||
'id' => null,
|
||||
] )->all();
|
||||
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields) );
|
||||
|
@ -1,123 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\ViewComposers\Components;
|
||||
|
||||
use App\DataProviders\CAProvinces;
|
||||
use App\DataProviders\USStates;
|
||||
use Illuminate\View\Component;
|
||||
use App\Models\ClientContact;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\View\View;
|
||||
|
||||
|
||||
// Contact Component
|
||||
class ContactComponent extends Component
|
||||
{
|
||||
|
||||
public function __construct(ClientContact $contact) {
|
||||
$contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
|
||||
'home_phone' =>$contact->client->phone,
|
||||
'custom_identifier' => $contact->client->number,
|
||||
'name' =>$contact->client->name,
|
||||
'id' => null
|
||||
] )->all();
|
||||
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields) );
|
||||
}
|
||||
|
||||
private $fields = [
|
||||
'name',
|
||||
'email',
|
||||
'home_phone',
|
||||
'phone',
|
||||
'custom_identifier',
|
||||
'customer_type' ,
|
||||
'id'
|
||||
];
|
||||
|
||||
private $defaults = [
|
||||
'customer_type' => "Business",
|
||||
'customer_identifier' => null,
|
||||
'id' => null
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.contact', array_merge($this->defaults, $this->attributes->getAttributes() ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Address Component
|
||||
class AddressComponent extends Component
|
||||
{
|
||||
private $fields = [
|
||||
'address_1',
|
||||
'address_2',
|
||||
'city',
|
||||
'postal_code',
|
||||
'province_code',
|
||||
'country'
|
||||
];
|
||||
|
||||
private $defaults = [
|
||||
'country' => 'US'
|
||||
];
|
||||
|
||||
public array $address;
|
||||
|
||||
public function __construct(array $address) {
|
||||
$this->address = $address;
|
||||
if(strlen($this->address['state']) > 2 ) {
|
||||
$this->address['state'] = $this->address['country'] == 'US' ? array_search($this->address['state'], USStates::$states) : CAProvinces::getAbbreviation($this->address['state']);
|
||||
}
|
||||
|
||||
$this->attributes = $this->newAttributeBag(
|
||||
Arr::only(Arr::mapWithKeys($this->address, function ($item, $key) {
|
||||
return in_array($key, ['address1','address2','state'])?[ (['address1'=>'address_1','address2'=>'address_2','state'=>'province_code'])[$key] => $item ] :[ $key => $item ];
|
||||
}),
|
||||
$this->fields) );
|
||||
}
|
||||
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.address',array_merge( $this->defaults, $this->attributes->getAttributes() ) );
|
||||
}
|
||||
}
|
||||
|
||||
// AmericanBankInfo Component
|
||||
class AccountComponent extends Component
|
||||
{
|
||||
private $fields = [
|
||||
'bank_account_type',
|
||||
'routing_number',
|
||||
'institution_number',
|
||||
'transit_number',
|
||||
'bank_name',
|
||||
'country',
|
||||
'account_number'
|
||||
];
|
||||
|
||||
private $defaults = [
|
||||
'bank_account_type' => null,
|
||||
'routing_number' => null,
|
||||
'institution_number' => null,
|
||||
'transit_number' => null,
|
||||
'bank_name' => ' ',
|
||||
'account_number' => null,
|
||||
'country' => 'US',
|
||||
"authorization_type" => 'Online'
|
||||
];
|
||||
|
||||
public array $account;
|
||||
|
||||
public function __construct(array $account) {
|
||||
$this->account = $account;
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($this->account, $this->fields) );
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.account', array_merge($this->attributes->getAttributes(), $this->defaults) );
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Cron;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class UpdateCalculatedFields
|
||||
{
|
||||
use Dispatchable;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
nlog("Updating calculated fields");
|
||||
|
||||
Auth::logout();
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
|
||||
Project::query()->with('tasks')->whereHas('tasks', function ($query) {
|
||||
$query->where('updated_at', '>', now()->subHours(2));
|
||||
})
|
||||
->cursor()
|
||||
->each(function ($project) {
|
||||
|
||||
$project->current_hours = $this->calculateDuration($project);
|
||||
$project->save();
|
||||
});
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
|
||||
Project::query()->with('tasks')->whereHas('tasks', function ($query) {
|
||||
$query->where('updated_at', '>', now()->subHours(2));
|
||||
})
|
||||
->cursor()
|
||||
->each(function ($project) {
|
||||
$project->current_hours = $this->calculateDuration($project);
|
||||
$project->save();
|
||||
});
|
||||
|
||||
//Clean password resets table
|
||||
\DB::connection($db)->table('password_resets')->where('created_at', '<', now()->subHour())->delete();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function calculateDuration($project): int
|
||||
{
|
||||
$duration = 0;
|
||||
|
||||
$project->tasks->each(function ($task) use (&$duration) {
|
||||
|
||||
if(is_iterable(json_decode($task->time_log))) {
|
||||
|
||||
foreach(json_decode($task->time_log) as $log) {
|
||||
|
||||
if(!is_array($log))
|
||||
continue;
|
||||
|
||||
$start_time = $log[0];
|
||||
$end_time = $log[1] == 0 ? time() : $log[1];
|
||||
|
||||
$duration += $end_time - $start_time;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return (int) round(($duration / 60 / 60), 0);
|
||||
|
||||
}
|
||||
}
|
@ -94,7 +94,9 @@ class ProcessMailgunWebhook implements ShouldQueue
|
||||
}
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($this->request['event-data']['tags'][0]);
|
||||
$company = Company::query()->where('company_key', $this->request['event-data']['tags'][0])->first();
|
||||
|
||||
/** @var \App\Models\Company $company */
|
||||
$company = Company::where('company_key', $this->request['event-data']['tags'][0])->first();
|
||||
|
||||
if ($company && $this->request['event-data']['event'] == 'complained' && config('ninja.notification.slack')) {
|
||||
$company->notification(new EmailSpamNotification($company))->ninja();
|
||||
@ -195,7 +197,7 @@ class ProcessMailgunWebhook implements ShouldQueue
|
||||
'date' => \Carbon\Carbon::parse($this->request['event-data']['timestamp'])->format('Y-m-d H:i:s') ?? '',
|
||||
];
|
||||
|
||||
if($sl) {
|
||||
if($sl instanceof SystemLog) {
|
||||
$data = $sl->log;
|
||||
$data['history']['events'][] = $event;
|
||||
$this->updateSystemLog($sl, $data);
|
||||
|
@ -95,6 +95,8 @@ class CleanStaleInvoiceOrder implements ShouldQueue
|
||||
->each(function ($invoice) {
|
||||
$invoice->service()->removeUnpaidGatewayFees();
|
||||
});
|
||||
|
||||
\DB::connection($db)->table('password_resets')->where('created_at', '<', now()->subHours(12))->delete();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class TemplateEmail extends Mailable
|
||||
}
|
||||
|
||||
$link_string = '<ul>';
|
||||
|
||||
$link_string .= "<li>{ctrans('texts.download_files')}</li>";
|
||||
foreach ($this->build_email->getAttachmentLinks() as $link) {
|
||||
$link_string .= "<li>{$link}</li>";
|
||||
}
|
||||
|
@ -155,6 +155,7 @@ class CompanyGateway extends BaseModel
|
||||
'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9' => 322,
|
||||
'80af24a6a691230bbec33e930ab40666' => 323,
|
||||
'vpyfbmdrkqcicpkjqdusgjfluebftuva' => 324, //BTPay
|
||||
'91be24c7b792230bced33e930ac61676' => 325,
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
@ -137,7 +137,7 @@ class Task extends BaseModel
|
||||
// 'project',
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
protected $touches = ['project'];
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
|
@ -82,6 +82,7 @@ class TaskObserver
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_ARCHIVE_TASK, $task, $task->company)->delay(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -586,10 +586,6 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
$invoices = Invoice::query()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||
|
||||
// $invoices->each(function ($invoice) {
|
||||
// $invoice->service()->deletePdf();
|
||||
// });
|
||||
|
||||
$invoices->first()->invitations->each(function ($invitation) use ($nmo) {
|
||||
if ((bool) $invitation->contact->send_email !== false && $invitation->contact->email) {
|
||||
$nmo->to_user = $invitation->contact;
|
||||
|
155
app/PaymentDrivers/Rotessa/Jobs/TransactionReport.php
Normal file
155
app/PaymentDrivers/Rotessa/Jobs/TransactionReport.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Rotessa\Jobs;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SystemLog;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\PaymentHash;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Jobs\Mail\PaymentFailedMailer;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
||||
class TransactionReport implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
public $tries = 1; //number of retries
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
foreach(MultiDB::$dbs as $db)
|
||||
{
|
||||
MultiDB::setDB($db);
|
||||
|
||||
CompanyGateway::query()
|
||||
->where('gateway_key', '91be24c7b792230bced33e930ac61676')
|
||||
->cursor()
|
||||
->each(function ($cg){
|
||||
|
||||
$driver = $cg->driver()->init();
|
||||
|
||||
//Approved Transactions
|
||||
$transactions = $driver->gatewayRequest("get", "transaction_report", ['page' => 1, 'status' => 'Approved', 'start_date' => now()->subMonths(2)->format('Y-m-d')]);
|
||||
|
||||
if($transactions->successful())
|
||||
{
|
||||
$transactions = $transactions->json();
|
||||
nlog($transactions);
|
||||
|
||||
Payment::query()
|
||||
->where('company_id', $cg->company_id)
|
||||
->where('status_id', Payment::STATUS_PENDING)
|
||||
->whereIn('transaction_reference', array_column($transactions, "transaction_schedule_id"))
|
||||
->cursor()
|
||||
->each(function ($payment) use ($transactions) {
|
||||
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => collect($transactions)->where('id', $payment->transaction_reference)->first()->toArray(), 'data' => []],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_ROTESSA,
|
||||
$payment->client,
|
||||
$payment->company,
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
//Declined / Charged Back Transactions
|
||||
$declined_transactions = $driver->gatewayRequest("get", "transaction_report", ['page' => 1, 'status' => 'Declined', 'start_date' => now()->subMonths(2)->format('Y-m-d')]);
|
||||
$chargeback_transactions = $driver->gatewayRequest("get", "transaction_report", ['page' => 1, 'status' => 'Chargeback', 'start_date' => now()->subMonths(2)->format('Y-m-d')]);
|
||||
|
||||
if($declined_transactions->successful() && $chargeback_transactions->successful()) {
|
||||
|
||||
$transactions = array_merge($declined_transactions->json(), $chargeback_transactions->json());
|
||||
|
||||
nlog($transactions);
|
||||
|
||||
Payment::query()
|
||||
->where('company_id', $cg->company_id)
|
||||
->where('status_id', Payment::STATUS_PENDING)
|
||||
->whereIn('transaction_reference', array_column($transactions, "transaction_schedule_id"))
|
||||
->cursor()
|
||||
->each(function ($payment) use ($transactions){
|
||||
|
||||
|
||||
$client = $payment->client;
|
||||
|
||||
$payment->service()->deletePayment();
|
||||
|
||||
$payment->status_id = Payment::STATUS_FAILED;
|
||||
$payment->save();
|
||||
|
||||
$payment_hash = PaymentHash::query()->where('payment_id', $payment->id)->first();
|
||||
|
||||
if ($payment_hash) {
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($client->getMergedSettings()));
|
||||
App::setLocale($client->locale());
|
||||
|
||||
$error = ctrans('texts.client_payment_failure_body', [
|
||||
'invoice' => implode(',', $payment->invoices->pluck('number')->toArray()),
|
||||
'amount' => array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total, ]);
|
||||
} else {
|
||||
$error = 'Payment for '.$payment->client->present()->name()." for {$payment->amount} failed";
|
||||
}
|
||||
|
||||
PaymentFailedMailer::dispatch(
|
||||
$payment_hash,
|
||||
$client->company,
|
||||
$client,
|
||||
$error
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => collect($transactions)->where('id', $payment->transaction_reference)->first()->toArray(), 'data' => []],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_ROTESSA,
|
||||
$payment->client,
|
||||
$payment->company,
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -37,11 +37,9 @@ use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
|
||||
class PaymentMethod implements MethodInterface
|
||||
{
|
||||
protected RotessaPaymentDriver $rotessa;
|
||||
|
||||
public function __construct(RotessaPaymentDriver $rotessa)
|
||||
public function __construct(protected RotessaPaymentDriver $rotessa)
|
||||
{
|
||||
$this->rotessa = $rotessa;
|
||||
$this->rotessa->init();
|
||||
}
|
||||
|
||||
@ -60,9 +58,6 @@ class PaymentMethod implements MethodInterface
|
||||
'id' => null
|
||||
] )->all();
|
||||
$data['gateway'] = $this->rotessa;
|
||||
// Set gateway type according to client country
|
||||
// $data['gateway_type_id'] = $data['client']->country->iso_3166_2 == 'US' ? GatewayType::BANK_TRANSFER : ( $data['client']->country->iso_3166_2 == 'CA' ? GatewayType::ACSS : (int) request('method'));
|
||||
// TODO: detect GatewayType based on client country USA vs CAN
|
||||
$data['gateway_type_id'] = GatewayType::ACSS ;
|
||||
$data['account'] = [
|
||||
'routing_number' => $data['client']->routing_id,
|
||||
@ -86,7 +81,7 @@ class PaymentMethod implements MethodInterface
|
||||
'country' => ['required'],
|
||||
'name' => ['required'],
|
||||
'address_1' => ['required'],
|
||||
'address_2' => ['required'],
|
||||
// 'address_2' => ['required'],
|
||||
'city' => ['required'],
|
||||
'email' => ['required','email:filter'],
|
||||
'province_code' => ['required','size:2','alpha'],
|
||||
@ -95,7 +90,7 @@ class PaymentMethod implements MethodInterface
|
||||
'account_number' => ['required'],
|
||||
'bank_name' => ['required'],
|
||||
'phone' => ['required'],
|
||||
'home_phone' => ['required'],
|
||||
'home_phone' => ['required','size:10'],
|
||||
'bank_account_type'=>['required_if:country,US'],
|
||||
'routing_number'=>['required_if:country,US'],
|
||||
'institution_number'=>['required_if:country,CA','numeric'],
|
||||
@ -104,6 +99,7 @@ class PaymentMethod implements MethodInterface
|
||||
'customer_id'=>['required_without:custom_identifier','integer'],
|
||||
]);
|
||||
$customer = new Customer( ['address' => $request->only('address_1','address_2','city','postal_code','province_code','country'), 'custom_identifier' => $request->input('custom_identifier') ] + $request->all());
|
||||
|
||||
$this->rotessa->findOrCreateCustomer($customer->resolve());
|
||||
|
||||
return redirect()->route('client.payment_methods.index')->withMessage(ctrans('texts.payment_method_added'));
|
||||
@ -112,7 +108,7 @@ class PaymentMethod implements MethodInterface
|
||||
return $this->rotessa->processInternallyFailedPayment($this->rotessa, new ClientPortalAuthorizationException( get_class( $e) . " : {$e->getMessage()}", (int) $e->getCode() ));
|
||||
}
|
||||
|
||||
return back()->withMessage(ctrans('texts.unable_to_verify_payment_method'));
|
||||
// return back()->withMessage(ctrans('texts.unable_to_verify_payment_method'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,12 +134,13 @@ class PaymentMethod implements MethodInterface
|
||||
* Handle payments page for Rotessa.
|
||||
*
|
||||
* @param PaymentResponseRequest $request
|
||||
* @return void
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
|
||||
$response= null;
|
||||
$customer = null;
|
||||
|
||||
try {
|
||||
$request->validate([
|
||||
'source' => ['required','string','exists:client_gateway_tokens,token'],
|
||||
@ -153,17 +150,23 @@ class PaymentMethod implements MethodInterface
|
||||
$customer = ClientGatewayToken::query()
|
||||
->where('company_gateway_id', $this->rotessa->company_gateway->id)
|
||||
->where('client_id', $this->rotessa->client->id)
|
||||
->where('is_deleted', 0)
|
||||
->where('token', $request->input('source'))
|
||||
->first();
|
||||
|
||||
if(!$customer) throw new \Exception('Client gateway token not found!', SystemLog::TYPE_ROTESSA);
|
||||
|
||||
$transaction = new Transaction($request->only('frequency' ,'installments','amount','process_date') + ['comment' => $this->rotessa->getDescription(false) ]);
|
||||
$transaction->additional(['customer_id' => $customer->gateway_customer_reference]);
|
||||
$transaction = array_filter( $transaction->resolve());
|
||||
$response = $this->rotessa->gateway->capture($transaction)->send();
|
||||
if(!$response->isSuccessful()) throw new \Exception($response->getMessage(), (int) $response->getCode());
|
||||
$response = $this->rotessa->gatewayRequest('post','transaction_schedules', $transaction);
|
||||
|
||||
if($response->failed())
|
||||
$response->throw();
|
||||
|
||||
return $this->processPendingPayment($response->getParameter('id'), (float) $response->getParameter('amount'), (int) $customer->gateway_type_id , $customer->token);
|
||||
$response = $response->json();
|
||||
nlog($response);
|
||||
return $this->processPendingPayment($response['id'], (float) $response['amount'], PaymentType::ACSS , $customer->token);
|
||||
} catch(\Throwable $e) {
|
||||
$this->processUnsuccessfulPayment( new InvalidResponseException($e->getMessage(), (int) $e->getCode()) );
|
||||
}
|
||||
@ -194,7 +197,7 @@ class PaymentMethod implements MethodInterface
|
||||
/**
|
||||
* Handle unsuccessful payment for Rotessa.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @param \Exception $exception
|
||||
* @throws PaymentFailed
|
||||
* @return void
|
||||
*/
|
||||
|
@ -72,9 +72,9 @@ class Client extends HttpClient
|
||||
$response = $this->httpClient->sendRequest( $this->requestFactory->createRequest($method, $uri, $headers, $body, $protocolVersion));
|
||||
else $response = $this->httpClient->request($method, $uri, compact('body','headers'));
|
||||
} catch (\Http\Client\Exception\NetworkException $networkException) {
|
||||
throw new NetworkException($networkException->getMessage(), $request, $networkException);
|
||||
throw new \Exception($networkException->getMessage());
|
||||
} catch (\Exception $exception) {
|
||||
throw new RequestException($exception->getMessage(), $request, $exception);
|
||||
throw new \Exception($exception->getMessage());
|
||||
}
|
||||
|
||||
return $response;
|
||||
|
@ -13,7 +13,7 @@ class PatchTransactionSchedulesId extends BaseRequest implements RequestInterfac
|
||||
public function setId(int $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
public function setAmount(int $value) {
|
||||
public function setAmount($value) {
|
||||
$this->setParameter('amount',$value);
|
||||
}
|
||||
public function setComment(string $value) {
|
||||
|
@ -15,7 +15,7 @@ class PostTransactionSchedulesUpdateViaPost extends BaseRequest implements Reque
|
||||
public function setId(int $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
public function setAmount(int $value) {
|
||||
public function setAmount($value) {
|
||||
$this->setParameter('amount',$value);
|
||||
}
|
||||
public function setComment(string $value) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\PaymentDrivers;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Omnipay\Omnipay;
|
||||
use App\Models\Client;
|
||||
use App\Models\Payment;
|
||||
@ -29,6 +30,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use App\PaymentDrivers\Rotessa\Resources\Customer;
|
||||
use App\PaymentDrivers\Rotessa\PaymentMethod as Acss;
|
||||
use App\PaymentDrivers\Rotessa\PaymentMethod as BankTransfer;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class RotessaPaymentDriver extends BaseDriver
|
||||
{
|
||||
@ -53,11 +55,6 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
|
||||
public function init(): self
|
||||
{
|
||||
|
||||
$this->gateway = Omnipay::create(
|
||||
$this->company_gateway->gateway->provider
|
||||
);
|
||||
$this->gateway->initialize((array) $this->company_gateway->getConfig());
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -116,30 +113,42 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
}
|
||||
|
||||
public function importCustomers() {
|
||||
$this->init();
|
||||
|
||||
try {
|
||||
if(!$result = Cache::has("rotessa-import_customers-{$this->company_gateway->company->company_key}")) {
|
||||
$result = $this->gateway->getCustomers()->send();
|
||||
if(!$result->isSuccessful()) throw new \Exception($result->getMessage(), (int) $result->getCode());
|
||||
// cache results
|
||||
Cache::put("rotessa-import_customers-{$this->company_gateway->company->company_key}", $result->getData(), 60 * 60 * 24);
|
||||
}
|
||||
|
||||
$result = Cache::get("rotessa-import_customers-{$this->company_gateway->company->company_key}");
|
||||
$customers = collect($result)->unique('email');
|
||||
$result = $this->gatewayRequest('get','customers',[]);
|
||||
|
||||
if($result->failed())
|
||||
$result->throw();
|
||||
|
||||
$customers = collect($result->json())->unique('email');
|
||||
|
||||
$client_emails = $customers->pluck('email')->all();
|
||||
$company_id = $this->company_gateway->company->id;
|
||||
// get existing customers
|
||||
$client_contacts = ClientContact::where('company_id', $company_id)->whereIn('email', $client_emails )->whereNull('deleted_at')->get();
|
||||
$client_contacts = ClientContact::where('company_id', $company_id)
|
||||
->whereIn('email', $client_emails )
|
||||
->whereHas('client', function ($q){
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->whereNull('deleted_at')
|
||||
->get();
|
||||
|
||||
$client_contacts = $client_contacts->map(function($item, $key) use ($customers) {
|
||||
return array_merge([], (array) $customers->firstWhere("email", $item->email) , ['custom_identifier' => $item->client->number, 'identifier' => $item->client->number, 'client_id' => $item->client->id ]);
|
||||
return array_merge($customers->firstWhere("email", $item->email),['custom_identifier' => $item->client->number, 'identifier' => $item->client->number, 'client_id' => $item->client->id ]);
|
||||
} );
|
||||
|
||||
// create payment methods
|
||||
$client_contacts->each(
|
||||
function($contact) use ($customers) {
|
||||
$result = $this->gateway->getCustomersId(['id' => ($contact = (object) $contact)->id])->send();
|
||||
function($contact) {
|
||||
// $result = $this->gateway->getCustomersId(['id' => ($contact = (object) $contact)->id])->send();
|
||||
$contact = (object)$contact;
|
||||
|
||||
$result = $this->gatewayRequest("get","customers/{$contact->id}");
|
||||
$result = $result->json();
|
||||
|
||||
$this->client = Client::find($contact->client_id);
|
||||
$customer = (new Customer($result->getData()))->additional(['id' => $contact->id, 'custom_identifier' => $contact->custom_identifier ] );
|
||||
|
||||
$customer = (new Customer($result))->additional(['id' => $contact->id, 'custom_identifier' => $contact->custom_identifier ] );
|
||||
$this->findOrCreateCustomer($customer->additional + $customer->jsonSerialize());
|
||||
}
|
||||
);
|
||||
@ -149,8 +158,8 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
$client_contacts = $customers->filter(function ($value, $key) use ($client_emails) {
|
||||
return !in_array(((object) $value)->email, $client_emails);
|
||||
})->each( function($customer) use ($company_id) {
|
||||
// create new client contact from rotess customer
|
||||
$customer = (object) $this->gateway->getCustomersId(['id' => ($customer = (object) $customer)->id])->send()->getData();
|
||||
|
||||
$customer = $this->gatewayRequest("get", "customers/{$customer['id']}")->json();
|
||||
/**
|
||||
{
|
||||
"account_number": "11111111"
|
||||
@ -183,6 +192,9 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
"updated_at": "2015-02-10T23:50:45.000-06:00"
|
||||
}
|
||||
*/
|
||||
$settings = ClientSettings::defaults();
|
||||
$settings->currency_id = $this->company_gateway->company->getSetting('currency_id');
|
||||
$customer = (object)$customer;
|
||||
$client = (\App\Factory\ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id))->fill(
|
||||
[
|
||||
'address1' => $customer->address['address_1'] ?? '',
|
||||
@ -192,7 +204,8 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
'state' => $customer->address['province_code'] ?? '',
|
||||
'country_id' => empty($customer->transit_number) ? 840 : 124,
|
||||
'routing_id' => empty(($r = $customer->routing_number))? null : $r,
|
||||
"number" => str_pad($customer->account_number,3,'0',STR_PAD_LEFT)
|
||||
"number" => str_pad($customer->account_number,3,'0',STR_PAD_LEFT),
|
||||
"settings" => $settings,
|
||||
]
|
||||
);
|
||||
$client->saveQuietly();
|
||||
@ -234,26 +247,34 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
$existing = ClientGatewayToken::query()
|
||||
->where('company_gateway_id', $this->company_gateway->id)
|
||||
->where('client_id', $this->client->id)
|
||||
->where('is_deleted',0)
|
||||
->orWhere(function (Builder $query) use ($data) {
|
||||
$query->where('token', encrypt(join(".", Arr::only($data, 'id','custom_identifier'))) )
|
||||
$query->where('token', join(".", Arr::only($data, ['id','custom_identifier'])))
|
||||
->where('gateway_customer_reference', Arr::only($data,'id'));
|
||||
})
|
||||
->exists();
|
||||
if ($existing) return true;
|
||||
if ($existing)
|
||||
return true;
|
||||
else if(!Arr::has($data,'id')) {
|
||||
$result = $this->gateway->authorize($data)->send();
|
||||
if (!$result->isSuccessful()) throw new \Exception($result->getMessage(), (int) $result->getCode());
|
||||
// $result = $this->gateway->authorize($data)->send();
|
||||
// if (!$result->isSuccessful()) throw new \Exception($result->getMessage(), (int) $result->getCode());
|
||||
|
||||
$customer = new Customer($result->getData());
|
||||
$result = $this->gatewayRequest('post', 'customers', $data);
|
||||
|
||||
if($result->failed())
|
||||
$result->throw();
|
||||
|
||||
$customer = new Customer($result->json());
|
||||
$data = array_filter($customer->resolve());
|
||||
|
||||
}
|
||||
|
||||
// $payment_method_id = Arr::has($data,'address.postal_code') && ((int) $data['address']['postal_code'])? GatewayType::BANK_TRANSFER: GatewayType::ACSS;
|
||||
// TODO: Check/ Validate postal code between USA vs CAN
|
||||
$payment_method_id = GatewayType::ACSS;
|
||||
$gateway_token = $this->storeGatewayToken( [
|
||||
'payment_meta' => $data + ['brand' => 'Rotessa', 'last4' => $data['bank_name'], 'type' => $data['bank_account_type'] ],
|
||||
'token' => encrypt(join(".", Arr::only($data, 'id','custom_identifier'))),
|
||||
'payment_meta' => $data + ['brand' => 'Bank Transfer', 'last4' => substr($data['account_number'], -4), 'type' => GatewayType::ACSS ],
|
||||
'token' => join(".", Arr::only($data, ['id','custom_identifier'])),
|
||||
'payment_method_id' => $payment_method_id ,
|
||||
], ['gateway_customer_reference' =>
|
||||
$data['id']
|
||||
@ -261,7 +282,6 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
|
||||
return $data['id'];
|
||||
|
||||
throw new \Exception($result->getMessage(), (int) $result->getCode());
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$data = [
|
||||
@ -269,7 +289,7 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
'transaction_response' => $th->getMessage(),
|
||||
'success' => false,
|
||||
'description' => $th->getMessage(),
|
||||
'code' =>(int) $th->getCode()
|
||||
'code' => 500
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(['server_response' => is_null($result) ? '' : $result->getMessage(), 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, 880 , $this->client, $this->company_gateway->company);
|
||||
@ -277,4 +297,20 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
public function gatewayRequest($verb, $uri, $payload = [])
|
||||
{
|
||||
$r = Http::withToken($this->company_gateway->getConfigField('apiKey'))
|
||||
->{$verb}($this->getUrl().$uri, $payload);
|
||||
|
||||
nlog($r->body());
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
private function getUrl(): string
|
||||
{
|
||||
return $this->company_gateway->getConfigField('testMode') ? 'https://sandbox-api.rotessa.com/v1/' : 'https://api.rotessa.com/v1/';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
if (Ninja::isSelfHost()) {
|
||||
return Limit::none();
|
||||
} else {
|
||||
return Limit::perMinute(300)->by($request->ip());
|
||||
return Limit::perMinute(500)->by($request->ip());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -46,16 +46,16 @@ class TaskRepository extends BaseRepository
|
||||
$this->new_task = false;
|
||||
}
|
||||
|
||||
if(isset($data['assigned_user_id']) && $data['assigned_user_id'] != $task->assigned_user_id){
|
||||
TaskAssigned::dispatch($task, $task->company->db)->delay(2);
|
||||
}
|
||||
|
||||
if(!is_numeric($task->rate) && !isset($data['rate']))
|
||||
$data['rate'] = 0;
|
||||
|
||||
$task->fill($data);
|
||||
$task->saveQuietly();
|
||||
|
||||
if(isset($data['assigned_user_id']) && $data['assigned_user_id'] != $task->assigned_user_id) {
|
||||
TaskAssigned::dispatch($task, $task->company->db)->delay(2);
|
||||
}
|
||||
|
||||
$this->init($task);
|
||||
|
||||
if ($this->new_task && ! $task->status_id) {
|
||||
@ -155,6 +155,8 @@ class TaskRepository extends BaseRepository
|
||||
$this->saveDocuments($data['documents'], $task);
|
||||
}
|
||||
|
||||
$this->calculateProjectDuration($task);
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
@ -261,6 +263,8 @@ class TaskRepository extends BaseRepository
|
||||
$task->saveQuietly();
|
||||
}
|
||||
|
||||
$this->calculateProjectDuration($task);
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
@ -302,7 +306,10 @@ class TaskRepository extends BaseRepository
|
||||
$task->saveQuietly();
|
||||
}
|
||||
|
||||
$this->calculateProjectDuration($task);
|
||||
|
||||
return $task;
|
||||
|
||||
}
|
||||
|
||||
public function triggeredActions($request, $task)
|
||||
@ -348,4 +355,67 @@ class TaskRepository extends BaseRepository
|
||||
|
||||
return $task->number;
|
||||
}
|
||||
|
||||
private function calculateProjectDuration(Task $task)
|
||||
{
|
||||
|
||||
if($task->project) {
|
||||
|
||||
$duration = 0;
|
||||
|
||||
$task->project->tasks->each(function ($task) use (&$duration) {
|
||||
|
||||
if(is_iterable(json_decode($task->time_log))) {
|
||||
|
||||
foreach(json_decode($task->time_log) as $log) {
|
||||
|
||||
if(!is_array($log)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start_time = $log[0];
|
||||
$end_time = $log[1] == 0 ? time() : $log[1];
|
||||
|
||||
$duration += $end_time - $start_time;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$task->project->current_hours = (int) round(($duration / 60 / 60), 0);
|
||||
$task->push();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
*/
|
||||
public function restore($task)
|
||||
{
|
||||
if (!$task->trashed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::restore($task);
|
||||
|
||||
$this->calculateProjectDuration($task);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
*/
|
||||
public function delete($task)
|
||||
{
|
||||
if ($task->is_deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::delete($task);
|
||||
|
||||
$this->calculateProjectDuration($task);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
58
composer.lock
generated
58
composer.lock
generated
@ -535,16 +535,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.316.10",
|
||||
"version": "3.317.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "eeb8df6ff6caa428e8bcd631ad2a96430900a249"
|
||||
"reference": "dc1e3031c2721a25beb2e8fbb175b576e3d60ab9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eeb8df6ff6caa428e8bcd631ad2a96430900a249",
|
||||
"reference": "eeb8df6ff6caa428e8bcd631ad2a96430900a249",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/dc1e3031c2721a25beb2e8fbb175b576e3d60ab9",
|
||||
"reference": "dc1e3031c2721a25beb2e8fbb175b576e3d60ab9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -624,9 +624,9 @@
|
||||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.316.10"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.317.1"
|
||||
},
|
||||
"time": "2024-07-30T18:10:20+00:00"
|
||||
"time": "2024-08-02T18:09:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@ -972,16 +972,16 @@
|
||||
},
|
||||
{
|
||||
"name": "checkout/checkout-sdk-php",
|
||||
"version": "3.2.1",
|
||||
"version": "3.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/checkout/checkout-sdk-php.git",
|
||||
"reference": "91797beb18fd9b1581b1cfe5b96a551c0009417c"
|
||||
"reference": "ac757648271894e3c30b7bc58ff08ba1b5b84de8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/91797beb18fd9b1581b1cfe5b96a551c0009417c",
|
||||
"reference": "91797beb18fd9b1581b1cfe5b96a551c0009417c",
|
||||
"url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/ac757648271894e3c30b7bc58ff08ba1b5b84de8",
|
||||
"reference": "ac757648271894e3c30b7bc58ff08ba1b5b84de8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1034,9 +1034,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/checkout/checkout-sdk-php/issues",
|
||||
"source": "https://github.com/checkout/checkout-sdk-php/tree/3.2.1"
|
||||
"source": "https://github.com/checkout/checkout-sdk-php/tree/3.2.2"
|
||||
},
|
||||
"time": "2024-07-09T16:07:18+00:00"
|
||||
"time": "2024-08-02T08:07:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "clue/stream-filter",
|
||||
@ -4758,16 +4758,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/pint",
|
||||
"version": "v1.17.0",
|
||||
"version": "v1.17.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/pint.git",
|
||||
"reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5"
|
||||
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
|
||||
"reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
|
||||
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4820,7 +4820,7 @@
|
||||
"issues": "https://github.com/laravel/pint/issues",
|
||||
"source": "https://github.com/laravel/pint"
|
||||
},
|
||||
"time": "2024-07-23T16:40:20+00:00"
|
||||
"time": "2024-08-01T09:06:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
@ -16968,16 +16968,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.11.8",
|
||||
"version": "1.11.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec"
|
||||
"reference": "e370bcddadaede0c1716338b262346f40d296f82"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec",
|
||||
"reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e370bcddadaede0c1716338b262346f40d296f82",
|
||||
"reference": "e370bcddadaede0c1716338b262346f40d296f82",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -17022,7 +17022,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-24T07:01:22+00:00"
|
||||
"time": "2024-08-01T16:25:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@ -19031,16 +19031,16 @@
|
||||
},
|
||||
{
|
||||
"name": "spatie/flare-client-php",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/flare-client-php.git",
|
||||
"reference": "097040ff51e660e0f6fc863684ac4b02c93fa234"
|
||||
"reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234",
|
||||
"reference": "097040ff51e660e0f6fc863684ac4b02c93fa234",
|
||||
"url": "https://api.github.com/repos/spatie/flare-client-php/zipball/180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122",
|
||||
"reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -19058,7 +19058,7 @@
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.0|^5.0"
|
||||
"spatie/pest-plugin-snapshots": "^1.0|^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@ -19088,7 +19088,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/flare-client-php/issues",
|
||||
"source": "https://github.com/spatie/flare-client-php/tree/1.7.0"
|
||||
"source": "https://github.com/spatie/flare-client-php/tree/1.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -19096,7 +19096,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-12T14:39:14+00:00"
|
||||
"time": "2024-08-01T08:27:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/ignition",
|
||||
|
@ -17,8 +17,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => env('APP_VERSION', '5.10.16'),
|
||||
'app_tag' => env('APP_TAG', '5.10.16'),
|
||||
'app_version' => env('APP_VERSION', '5.10.17'),
|
||||
'app_tag' => env('APP_TAG', '5.10.17'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
13780
openapi/api-docs.yaml
13780
openapi/api-docs.yaml
File diff suppressed because it is too large
Load Diff
@ -998,7 +998,7 @@ paths:
|
||||
post:
|
||||
tags:
|
||||
- bank_transactions
|
||||
summary: "Performs bulk actions on an array of bank_transations"
|
||||
summary: "Bulk actions"
|
||||
description: ""
|
||||
operationId: bulkBankTransactions
|
||||
parameters:
|
||||
@ -1042,8 +1042,30 @@ paths:
|
||||
post:
|
||||
tags:
|
||||
- bank_transactions
|
||||
summary: "Performs match actions on an array of bank_transactions"
|
||||
description: ""
|
||||
summary: "Match transactions"
|
||||
description: |
|
||||
Matching invoices or a payment to a bank transactions.
|
||||
|
||||
The API expects the id of the transaction along with either a comma separated list of invoice ids OR the payment id to associate the transaction to.
|
||||
|
||||
Example for matching a transaction to two invoices:
|
||||
|
||||
{"transactions":[{"id":"olejRl5ejN","invoice_ids":"JxboYBLegw,JxboYBLeXX"}]}
|
||||
|
||||
Example for matching a transaction and a paymente:
|
||||
|
||||
{"transactions":[{"id":"olejRl5ejN","payment_id":"JxboYBLeXf"}]}
|
||||
|
||||
Matching expenses.
|
||||
|
||||
You can match an existing expense within Invoice Ninja - or - create a new expense using the following:
|
||||
|
||||
{"transactions":[{"id":"open5pld7A","vendor_id":"gl9avJnaG1","ninja_category_id":""}]}
|
||||
|
||||
To match to an existing expense:
|
||||
|
||||
{"transactions":[{"id":"Jxbo2qKagw","expense_id":"7N1aMM1aWm"}]}
|
||||
|
||||
operationId: matchBankTransactions
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/X-API-TOKEN"
|
||||
|
@ -22,6 +22,7 @@
|
||||
color-scheme: light dark;
|
||||
supported-color-schemes: light dark;
|
||||
}
|
||||
|
||||
@if(isset($settings) && $settings->email_style === 'dark')
|
||||
body {
|
||||
background-color: #1a1a1a !important;
|
||||
@ -48,6 +49,13 @@
|
||||
hr {
|
||||
border-color: #474849 !important;
|
||||
}
|
||||
.file_icon {
|
||||
filter: invert(1);
|
||||
}
|
||||
@else
|
||||
.file_icon {
|
||||
filter: invert(1);
|
||||
}
|
||||
@endif
|
||||
/** Content-specific styles. **/
|
||||
#content .button {
|
||||
@ -171,9 +179,12 @@
|
||||
|
||||
@isset($links)
|
||||
<div>
|
||||
<ul style="list-style-type: none;">
|
||||
<ul style="list-style-type: none;">
|
||||
@if(count($links) > 0)
|
||||
<li>{{ ctrans('texts.download_files')}}</li>
|
||||
@endif
|
||||
@foreach($links as $link)
|
||||
<li>{!! $link ?? '' !!} <img height="15px" src="{{ asset('images/svg/dark/file.svg') }}"></li>
|
||||
<li>{!! $link ?? '' !!} <img height="15px" src="{{ asset('images/svg/dark/file.svg') }}" class="file_icon"></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@
|
||||
{{ ctrans('texts.address2') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input class="input w-full" id="address_2" name="address_2" type="text" placeholder="Address Line 2" required value="{{ old('address_2', $address_2) }}">
|
||||
<input class="input w-full" id="address_2" name="address_2" type="text" placeholder="Address Line 2" value="{{ old('address_2', $address_2) }}">
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user