mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 17:24:36 -04:00
Merge pull request #27 from M-E-Development-Design/v5-develop
V5 develop
This commit is contained in:
commit
ae75f7228c
@ -1 +1 @@
|
||||
5.10.16
|
||||
5.10.19
|
@ -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();
|
||||
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?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\DataProviders;
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\DataProviders;
|
||||
|
||||
use Omnipay\Rotessa\Object\Frequency;
|
||||
|
||||
final class Frequencies
|
||||
{
|
||||
public static function get() : array {
|
||||
return Frequency::getTypes();
|
||||
}
|
||||
|
||||
public static function getFromType() {
|
||||
|
||||
}
|
||||
public static function getOnePayment() {
|
||||
return Frequency::ONCE;
|
||||
}
|
||||
}
|
@ -22,5 +22,5 @@
|
||||
*/
|
||||
function ctrans(string $string, $replace = [], $locale = null): string
|
||||
{
|
||||
return trans($string, $replace, $locale);
|
||||
return html_entity_decode(trans($string, $replace, $locale));
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class NinjaPlanController extends Controller
|
||||
|
||||
$data['intent'] = $setupIntent;
|
||||
$data['client'] = Auth::guard('contact')->user()->client;
|
||||
|
||||
|
||||
return $this->render('plan.trial', $data);
|
||||
}
|
||||
|
||||
@ -88,6 +88,8 @@ class NinjaPlanController extends Controller
|
||||
{
|
||||
$trial_started = "Trial Started @ ".now()->format('Y-m-d H:i:s');
|
||||
|
||||
auth()->guard('contact')->user()->fill($request->only(['first_name','last_name']))->save();
|
||||
|
||||
$client = auth()->guard('contact')->user()->client;
|
||||
$client->private_notes = $trial_started;
|
||||
$client->fill($request->all());
|
||||
|
@ -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);
|
||||
|
@ -59,9 +59,9 @@ class ExportController extends BaseController
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$hash = Str::uuid();
|
||||
$hash = Str::uuid()->toString();
|
||||
$url = \Illuminate\Support\Facades\URL::temporarySignedRoute('protected_download', now()->addHour(), ['hash' => $hash]);
|
||||
Cache::put($hash, $url, now()->addHour());
|
||||
Cache::put($hash, $url, 3600);
|
||||
|
||||
CompanyExport::dispatch($user->getCompany(), $user, $hash);
|
||||
|
||||
|
@ -298,6 +298,9 @@ class PreviewController extends BaseController
|
||||
->mock();
|
||||
} catch(SyntaxError $e) {
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
return response()->json(['message' => 'invalid data access', 'errors' => ['design.design.body' => $e->getMessage()]], 422);
|
||||
}
|
||||
|
||||
if (request()->query('html') == 'true') {
|
||||
return $ts->getHtml();
|
||||
|
@ -14,7 +14,7 @@ namespace App\Http\Controllers\VendorPortal;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\TranslationHelper;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VendorContactController extends Controller
|
||||
{
|
||||
@ -58,14 +58,14 @@ class VendorContactController extends Controller
|
||||
'settings' => $vendor_contact->vendor->company->settings,
|
||||
'company' => $vendor_contact->vendor->company,
|
||||
'sidebar' => $this->sidebarMenu(),
|
||||
'countries' => TranslationHelper::getCountries(),
|
||||
'countries' => app('countries'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(VendorContact $vendor_contact)
|
||||
public function update(Request $request, VendorContact $vendor_contact)
|
||||
{
|
||||
$vendor_contact->fill(request()->all());
|
||||
$vendor_contact->vendor->fill(request()->all());
|
||||
$vendor_contact->fill($request->all());
|
||||
$vendor_contact->vendor->fill($request->all());
|
||||
$vendor_contact->push();
|
||||
|
||||
return back()->withSuccess(ctrans('texts.profile_updated_successfully'));
|
||||
@ -76,16 +76,10 @@ class VendorContactController extends Controller
|
||||
$enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;
|
||||
$data = [];
|
||||
|
||||
// TODO: Enable dashboard once it's completed.
|
||||
// $this->settings->enable_client_portal_dashboard
|
||||
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
|
||||
|
||||
if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) {
|
||||
$data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text'];
|
||||
}
|
||||
|
||||
// $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ class ContactRegister
|
||||
// As a fallback for self-hosted, it will use default company in the system
|
||||
// if key isn't provided in the url.
|
||||
if (! $request->route()->parameter('company_key') && Ninja::isSelfHost()) {
|
||||
$company = Account::query()->first()->default_company;
|
||||
$company = Account::query()->first()->default_company ?? Account::query()->first()->companies->first();
|
||||
|
||||
if (! $company->client_can_register) {
|
||||
abort(400, 'Registration disabled');
|
||||
|
@ -4,15 +4,15 @@ namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Cache\RateLimiter;
|
||||
use Illuminate\Contracts\Redis\Factory as Redis;
|
||||
use Illuminate\Redis\Limiters\DurationLimiter;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
|
||||
class ThrottleRequestsWithPredis extends ThrottleRequests
|
||||
class ThrottleRequestsWithPredis extends \Illuminate\Routing\Middleware\ThrottleRequests
|
||||
{
|
||||
/**
|
||||
* The Redis factory implementation.
|
||||
*
|
||||
* @var \Illuminate\Redis\Connections\Connection
|
||||
* @var \Illuminate\Contracts\Redis\Factory
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
@ -32,14 +32,14 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
|
||||
|
||||
/**
|
||||
* Create a new request throttler.
|
||||
*
|
||||
* @param \Illuminate\Cache\RateLimiter $limiter
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(RateLimiter $limiter)
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
public function __construct(RateLimiter $limiter, Redis $redis)
|
||||
{
|
||||
parent::__construct($limiter);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->redis = \Illuminate\Support\Facades\Redis::connection('sentinel-cache');
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
|
||||
protected function handleRequest($request, Closure $next, array $limits)
|
||||
{
|
||||
foreach ($limits as $limit) {
|
||||
if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decayMinutes)) {
|
||||
if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) {
|
||||
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
|
||||
}
|
||||
}
|
||||
@ -79,16 +79,13 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $maxAttempts
|
||||
* @param int $decayMinutes
|
||||
* @param int $decaySeconds
|
||||
* @return mixed
|
||||
*/
|
||||
protected function tooManyAttempts($key, $maxAttempts, $decayMinutes)
|
||||
protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
|
||||
{
|
||||
$limiter = new DurationLimiter(
|
||||
$this->redis,
|
||||
$key,
|
||||
$maxAttempts,
|
||||
$decayMinutes * 60
|
||||
$this->getRedisConnection(), $key, $maxAttempts, $decaySeconds
|
||||
);
|
||||
|
||||
return tap(! $limiter->acquire(), function () use ($key, $limiter) {
|
||||
@ -121,4 +118,13 @@ class ThrottleRequestsWithPredis extends ThrottleRequests
|
||||
{
|
||||
return $this->decaysAt[$key] - $this->currentTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Redis connection that should be used for throttling.
|
||||
*
|
||||
*/
|
||||
protected function getRedisConnection()
|
||||
{
|
||||
return $this->redis;
|
||||
}
|
||||
}
|
||||
|
@ -31,12 +31,8 @@ class TwigLint implements ValidationRule
|
||||
try {
|
||||
$twig->parse($twig->tokenize(new \Twig\Source(preg_replace('/<!--.*?-->/s', '', $value), '')));
|
||||
} catch (\Twig\Error\SyntaxError $e) {
|
||||
// echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||
nlog($e->getMessage());
|
||||
$fail($e->getMessage());
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?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\Http\ViewComposers\Components\Rotessa;
|
||||
|
||||
@ -27,16 +36,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) );
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?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\Http\ViewComposers\Components\Rotessa;
|
||||
|
||||
@ -25,10 +34,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']);
|
||||
}
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?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\Http\ViewComposers\Components\Rotessa;
|
||||
|
||||
@ -18,9 +27,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) );
|
||||
}
|
||||
}
|
@ -88,11 +88,11 @@ class PortalComposer
|
||||
$data['sidebar'] = $this->sidebarMenu();
|
||||
$data['header'] = [];
|
||||
$data['footer'] = [];
|
||||
$data['countries'] = TranslationHelper::getCountries();
|
||||
$data['countries'] = app('countries');
|
||||
$data['company'] = auth()->guard('contact')->user()->company;
|
||||
$data['client'] = auth()->guard('contact')->user()->client;
|
||||
$data['settings'] = $this->settings;
|
||||
$data['currencies'] = TranslationHelper::getCurrencies();
|
||||
$data['currencies'] = app('currencies');
|
||||
$data['contact'] = auth()->guard('contact')->user();
|
||||
|
||||
$data['multiple_contacts'] = session()->get('multiple_contacts') ?: collect();
|
||||
@ -136,11 +136,11 @@ class PortalComposer
|
||||
|
||||
$data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity'];
|
||||
|
||||
if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) {
|
||||
// if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) {
|
||||
$data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card'];
|
||||
} else {
|
||||
// } else {
|
||||
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
|
||||
}
|
||||
// }
|
||||
|
||||
if (auth()->guard('contact')->user()->client->getSetting('client_initiated_payments')) {
|
||||
$data[] = ['title' => ctrans('texts.pre_payment'), 'url' => 'client.pre_payments.index', 'icon' => 'dollar-sign'];
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\View;
|
||||
use App\DataProviders\CAProvinces;
|
||||
@ -9,7 +18,6 @@ View::composer(['*.rotessa.components.address','*.rotessa.components.banks.US.ba
|
||||
$view->with('states', $states);
|
||||
});
|
||||
|
||||
// CAProvinces View Composer
|
||||
View::composer(['*.rotessa.components.address','*.rotessa.components.banks.CA.bank','*.rotessa.components.dropdowns.country.CA'], function ($view) {
|
||||
$provinces = CAProvinces::get();
|
||||
$view->with('provinces', $provinces);
|
||||
|
@ -695,7 +695,7 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
$url = Cache::get($this->hash);
|
||||
|
||||
Cache::put($this->hash, $storage_path, now()->addHour());
|
||||
Cache::put($this->hash, $storage_path, 3600);
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
|
@ -48,12 +48,12 @@ class RecurringInvoicesCron
|
||||
Auth::logout();
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$recurring_invoices = RecurringInvoice::query()->where('recurring_invoices.status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('recurring_invoices.is_deleted', false)
|
||||
->where('recurring_invoices.remaining_cycles', '!=', '0')
|
||||
->whereNotNull('recurring_invoices.next_send_date')
|
||||
->whereNull('recurring_invoices.deleted_at')
|
||||
->where('recurring_invoices.next_send_date', '<=', now()->toDateTimeString())
|
||||
$recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('is_deleted', false)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereNotNull('next_send_date')
|
||||
->whereNull('deleted_at')
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
@ -87,27 +87,18 @@ class RecurringInvoicesCron
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$recurring_invoices = RecurringInvoice::query()->where('recurring_invoices.status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('recurring_invoices.is_deleted', false)
|
||||
->where('recurring_invoices.remaining_cycles', '!=', '0')
|
||||
->whereNull('recurring_invoices.deleted_at')
|
||||
->whereNotNull('recurring_invoices.next_send_date')
|
||||
->where('recurring_invoices.next_send_date', '<=', now()->toDateTimeString())
|
||||
// ->whereHas('client', function ($query) {
|
||||
// $query->where('is_deleted', 0)
|
||||
// ->where('deleted_at', null);
|
||||
// })
|
||||
// ->whereHas('company', function ($query) {
|
||||
// $query->where('is_disabled', 0);
|
||||
// })
|
||||
->leftJoin('clients', function ($join) {
|
||||
$join->on('recurring_invoices.client_id', '=', 'clients.id')
|
||||
->where('clients.is_deleted', 0)
|
||||
->whereNull('clients.deleted_at');
|
||||
$recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('is_deleted', false)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('next_send_date')
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
})
|
||||
->leftJoin('companies', function ($join) {
|
||||
$join->on('recurring_invoices.company_id', '=', 'companies.id')
|
||||
->where('companies.is_disabled', 0);
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->with('company')
|
||||
->cursor();
|
||||
|
@ -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);
|
||||
|
@ -176,8 +176,8 @@ class SendRecurring implements ShouldQueue
|
||||
private function createRecurringInvitations($invoice): Invoice
|
||||
{
|
||||
if ($this->recurring_invoice->invitations->count() == 0) {
|
||||
$this->recurring_invoice->service()->createInvitations()->save();
|
||||
$this->recurring_invoice = $this->recurring_invoice->fresh();
|
||||
$this->recurring_invoice = $this->recurring_invoice->service()->createInvitations()->save();
|
||||
// $this->recurring_invoice = $this->recurring_invoice->fresh();
|
||||
}
|
||||
|
||||
$this->recurring_invoice->invitations->each(function ($recurring_invitation) use ($invoice) {
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -61,10 +61,10 @@ class QuoteReminderJob implements ShouldQueue
|
||||
nrlog("Sending quote reminders on ".now()->format('Y-m-d h:i:s'));
|
||||
|
||||
Quote::query()
|
||||
->where('quotes.is_deleted', 0)
|
||||
->whereIn('quotes.status_id', [Invoice::STATUS_SENT])
|
||||
->whereNull('quotes.deleted_at')
|
||||
->where('quotes.next_send_date', '<=', now()->toDateTimeString())
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT])
|
||||
->whereNull('deleted_at')
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
@ -88,10 +88,10 @@ class QuoteReminderJob implements ShouldQueue
|
||||
nrlog("Sending quote reminders on db {$db} ".now()->format('Y-m-d h:i:s'));
|
||||
|
||||
Quote::query()
|
||||
->where('quotes.is_deleted', 0)
|
||||
->whereIn('quotes.status_id', [Invoice::STATUS_SENT])
|
||||
->whereNull('quotes.deleted_at')
|
||||
->where('quotes.next_send_date', '<=', now()->toDateTimeString())
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT])
|
||||
->whereNull('deleted_at')
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
|
@ -356,15 +356,14 @@ class BillingPortalPurchase extends Component
|
||||
|
||||
$this->methods = $contact->client->service()->getPaymentMethods($this->price);
|
||||
|
||||
foreach($this->methods as $method){
|
||||
|
||||
if($method['is_paypal'] == '1' && !$this->steps['check_rff']){
|
||||
$this->rff();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
$method_values = array_column($this->methods, 'is_paypal');
|
||||
$is_paypal = in_array('1', $method_values);
|
||||
|
||||
if($is_paypal && !$this->steps['check_rff'])
|
||||
$this->rff();
|
||||
elseif(!$this->steps['check_rff'])
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
|
||||
$this->heading_text = ctrans('texts.payment_methods');
|
||||
|
||||
return $this;
|
||||
|
@ -484,7 +484,7 @@ class BillingPortalPurchasev2 extends Component
|
||||
$this->methods = [];
|
||||
}
|
||||
|
||||
if ($this->contact && $this->float_amount_total >= 1) {
|
||||
if ($this->contact && $this->float_amount_total >= 0) {
|
||||
$this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total);
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,6 @@ class PersonalAddress extends Component
|
||||
|
||||
public $country_id;
|
||||
|
||||
public $countries;
|
||||
|
||||
public $saved;
|
||||
|
||||
protected $rules = [
|
||||
@ -33,7 +31,7 @@ class PersonalAddress extends Component
|
||||
'country_id' => ['sometimes'],
|
||||
];
|
||||
|
||||
public function mount($countries)
|
||||
public function mount()
|
||||
{
|
||||
$this->fill([
|
||||
'profile' => auth()->guard('contact')->user()->client,
|
||||
@ -43,8 +41,6 @@ class PersonalAddress extends Component
|
||||
'state' => auth()->guard('contact')->user()->client->state,
|
||||
'postal_code' => auth()->guard('contact')->user()->client->postal_code,
|
||||
'country_id' => auth()->guard('contact')->user()->client->country_id,
|
||||
|
||||
'countries' => $countries,
|
||||
'saved' => ctrans('texts.save'),
|
||||
]);
|
||||
}
|
||||
|
@ -20,8 +20,6 @@ class ShippingAddress extends Component
|
||||
|
||||
public $shipping_country_id;
|
||||
|
||||
public $countries;
|
||||
|
||||
public $saved;
|
||||
|
||||
protected $rules = [
|
||||
@ -33,7 +31,7 @@ class ShippingAddress extends Component
|
||||
'shipping_country_id' => ['sometimes'],
|
||||
];
|
||||
|
||||
public function mount($countries)
|
||||
public function mount()
|
||||
{
|
||||
$this->fill([
|
||||
'profile' => auth()->guard('contact')->user()->client,
|
||||
@ -43,8 +41,6 @@ class ShippingAddress extends Component
|
||||
'shipping_state' => auth()->guard('contact')->user()->client->shipping_state,
|
||||
'shipping_postal_code' => auth()->guard('contact')->user()->client->shipping_postal_code,
|
||||
'shipping_country_id' => auth()->guard('contact')->user()->client->shipping_country_id,
|
||||
|
||||
'countries' => $countries,
|
||||
'saved' => ctrans('texts.save'),
|
||||
]);
|
||||
}
|
||||
|
@ -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 = [];
|
||||
|
@ -53,4 +53,9 @@ class Currency extends StaticModel
|
||||
'deleted_at' => 'timestamp',
|
||||
'precision' => 'integer',
|
||||
];
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return trans('texts.currency_'.$this->name);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use App\Models\CompanyUser;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||
|
||||
/**
|
||||
* App\Models\Task
|
||||
@ -137,7 +138,7 @@ class Task extends BaseModel
|
||||
// 'project',
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
protected $touches = ['project'];
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
@ -159,27 +160,55 @@ class Task extends BaseModel
|
||||
return $this->morphMany(Document::class, 'documentable');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function assigned_user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(TaskStatus::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function stringStatus()
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(Invoice::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function stringStatus(): string
|
||||
{
|
||||
if($this->invoice_id) {
|
||||
return '<h5><span class="badge badge-success">'.ctrans('texts.invoiced').'</span></h5>';
|
||||
@ -193,16 +222,6 @@ class Task extends BaseModel
|
||||
|
||||
}
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(Invoice::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function calcStartTime()
|
||||
{
|
||||
$parts = json_decode($this->time_log) ?: [];
|
||||
@ -230,7 +249,7 @@ class Task extends BaseModel
|
||||
public function calcDuration($start_time_cutoff = 0, $end_time_cutoff = 0)
|
||||
{
|
||||
$duration = 0;
|
||||
$parts = json_decode($this->time_log) ?: [];
|
||||
$parts = json_decode($this->time_log ?? '{}') ?: [];
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$start_time = $part[0];
|
||||
@ -272,6 +291,26 @@ class Task extends BaseModel
|
||||
return $this->company->settings->default_task_rate ?? 0;
|
||||
}
|
||||
|
||||
public function taskCompanyValue(): float
|
||||
{
|
||||
$client_currency = $this->client->getSetting('currency_id');
|
||||
$company_currency = $this->company->getSetting('currency_id');
|
||||
|
||||
if($client_currency != $company_currency)
|
||||
{
|
||||
$converter = new CurrencyApi();
|
||||
return $converter->convert($this->taskValue(), $client_currency, $company_currency);
|
||||
}
|
||||
|
||||
return $this->taskValue();
|
||||
|
||||
}
|
||||
|
||||
public function taskValue(): float
|
||||
{
|
||||
return round(($this->calcDuration() / 3600) * $this->getRate(),2);
|
||||
}
|
||||
|
||||
public function processLogs()
|
||||
{
|
||||
|
||||
|
@ -82,6 +82,7 @@ class TaskObserver
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_ARCHIVE_TASK, $task, $task->company)->delay(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,7 @@ abstract class AbstractPaymentDriver
|
||||
{
|
||||
abstract public function authorizeView(array $data);
|
||||
|
||||
abstract public function authorizeResponse(Request $request);
|
||||
abstract public function authorizeResponse(\App\Http\Requests\Request | Request $request);
|
||||
|
||||
abstract public function processPaymentView(array $data);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1,249 +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\PaymentDrivers;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Omnipay\Common\Item;
|
||||
use Omnipay\Omnipay;
|
||||
|
||||
class PayPalExpressPaymentDriver extends BaseDriver
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $token_billing = false;
|
||||
|
||||
public $can_authorise_credit_card = false;
|
||||
|
||||
private $omnipay_gateway;
|
||||
|
||||
private float $fee = 0;
|
||||
|
||||
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL;
|
||||
|
||||
public function gatewayTypes()
|
||||
{
|
||||
return [
|
||||
GatewayType::PAYPAL,
|
||||
];
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Omnipay PayPal_Express gateway.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function initializeOmnipayGateway(): void
|
||||
{
|
||||
$this->omnipay_gateway = Omnipay::create(
|
||||
$this->company_gateway->gateway->provider
|
||||
);
|
||||
|
||||
$this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig());
|
||||
}
|
||||
|
||||
public function setPaymentMethod($payment_method_id)
|
||||
{
|
||||
// PayPal doesn't have multiple ways of paying.
|
||||
// There's just one, off-site redirect.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function authorizeView($payment_method)
|
||||
{
|
||||
// PayPal doesn't support direct authorization.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
// PayPal doesn't support direct authorization.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function processPaymentView($data)
|
||||
{
|
||||
$this->initializeOmnipayGateway();
|
||||
|
||||
$this->payment_hash->data = array_merge((array) $this->payment_hash->data, ['amount' => $data['total']['amount_with_fee']]);
|
||||
$this->payment_hash->save();
|
||||
|
||||
$response = $this->omnipay_gateway
|
||||
->purchase($this->generatePaymentDetails($data))
|
||||
->setItems($this->generatePaymentItems($data))
|
||||
->send();
|
||||
|
||||
if ($response->isRedirect()) {
|
||||
return $response->redirect();
|
||||
}
|
||||
|
||||
// $this->sendFailureMail($response->getMessage() ?: '');
|
||||
|
||||
$message = [
|
||||
'server_response' => $response->getMessage(),
|
||||
'data' => $this->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_PAYPAL,
|
||||
$this->client,
|
||||
$this->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($response->getMessage(), $response->getCode());
|
||||
}
|
||||
|
||||
public function processPaymentResponse($request)
|
||||
{
|
||||
$this->initializeOmnipayGateway();
|
||||
|
||||
$response = $this->omnipay_gateway
|
||||
->completePurchase(['amount' => $this->payment_hash->data->amount, 'currency' => $this->client->getCurrencyCode()])
|
||||
->send();
|
||||
|
||||
if ($response->isCancelled() && $this->client->getSetting('enable_client_portal')) {
|
||||
return redirect()->route('client.invoices.index')->with('warning', ctrans('texts.status_cancelled'));
|
||||
} elseif($response->isCancelled() && !$this->client->getSetting('enable_client_portal')) {
|
||||
redirect()->route('client.invoices.show', ['invoice' => $this->payment_hash->fee_invoice])->with('warning', ctrans('texts.status_cancelled'));
|
||||
}
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$data = [
|
||||
'payment_method' => $response->getData()['TOKEN'],
|
||||
'payment_type' => PaymentType::PAYPAL,
|
||||
'amount' => $this->payment_hash->data->amount,
|
||||
'transaction_reference' => $response->getTransactionReference(),
|
||||
'gateway_type_id' => GatewayType::PAYPAL,
|
||||
];
|
||||
|
||||
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => (array) $response->getData(), 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_PAYPAL,
|
||||
$this->client,
|
||||
$this->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
||||
if (! $response->isSuccessful()) {
|
||||
$data = $response->getData();
|
||||
|
||||
$this->sendFailureMail($response->getMessage() ?: '');
|
||||
|
||||
$message = [
|
||||
'server_response' => $data['L_LONGMESSAGE0'],
|
||||
'data' => $this->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_PAYPAL,
|
||||
$this->client,
|
||||
$this->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($response->getMessage(), $response->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
public function generatePaymentDetails(array $data)
|
||||
{
|
||||
$_invoice = collect($this->payment_hash->data->invoices)->first();
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||
|
||||
// $this->fee = $this->feeCalc($invoice, $data['total']['amount_with_fee']);
|
||||
|
||||
return [
|
||||
'currency' => $this->client->getCurrencyCode(),
|
||||
'transactionType' => 'Purchase',
|
||||
'clientIp' => request()->getClientIp(),
|
||||
// 'amount' => round(($data['total']['amount_with_fee'] + $this->fee),2),
|
||||
'amount' => round($data['total']['amount_with_fee'], 2),
|
||||
'returnUrl' => route('client.payments.response', [
|
||||
'company_gateway_id' => $this->company_gateway->id,
|
||||
'payment_hash' => $this->payment_hash->hash,
|
||||
'payment_method_id' => GatewayType::PAYPAL,
|
||||
]),
|
||||
'cancelUrl' => $this->client->company->domain()."/client/invoices/{$invoice->hashed_id}",
|
||||
'description' => implode(',', collect($this->payment_hash->data->invoices)
|
||||
->map(function ($invoice) {
|
||||
return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->invoice_number);
|
||||
})->toArray()),
|
||||
'transactionId' => $this->payment_hash->hash.'-'.time(),
|
||||
'ButtonSource' => 'InvoiceNinja_SP',
|
||||
'solutionType' => 'Sole',
|
||||
'no_shipping' => $this->company_gateway->require_shipping_address ? 0 : 1,
|
||||
];
|
||||
}
|
||||
|
||||
public function generatePaymentItems(array $data)
|
||||
{
|
||||
$_invoice = collect($this->payment_hash->data->invoices)->first();
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||
|
||||
$items = [];
|
||||
|
||||
$items[] = new Item([
|
||||
'name' => ' ',
|
||||
'description' => ctrans('texts.invoice_number').'# '.$invoice->number,
|
||||
'price' => $data['total']['amount_with_fee'],
|
||||
'quantity' => 1,
|
||||
]);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function feeCalc($invoice, $invoice_total)
|
||||
{
|
||||
$invoice->service()->removeUnpaidGatewayFees();
|
||||
$invoice = $invoice->fresh();
|
||||
|
||||
$balance = floatval($invoice->balance);
|
||||
|
||||
$_updated_invoice = $invoice->service()->addGatewayFee($this->company_gateway, GatewayType::PAYPAL, $invoice_total)->save();
|
||||
|
||||
if (floatval($_updated_invoice->balance) > $balance) {
|
||||
$fee = floatval($_updated_invoice->balance) - $balance;
|
||||
|
||||
$this->payment_hash->fee_total = $fee;
|
||||
$this->payment_hash->save();
|
||||
|
||||
return $fee;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
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,
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -12,14 +12,11 @@
|
||||
|
||||
namespace App\PaymentDrivers\Rotessa;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Client;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SystemLog;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Exceptions\PaymentFailed;
|
||||
@ -28,20 +25,20 @@ use App\Models\ClientGatewayToken;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use App\PaymentDrivers\RotessaPaymentDriver;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\PaymentDrivers\Rotessa\Resources\Customer;
|
||||
use App\PaymentDrivers\Rotessa\Resources\Transaction;
|
||||
use Omnipay\Common\Exception\InvalidRequestException;
|
||||
use Omnipay\Common\Exception\InvalidResponseException;
|
||||
use App\Exceptions\Ninja\ClientPortalAuthorizationException;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
|
||||
class PaymentMethod implements MethodInterface
|
||||
{
|
||||
protected RotessaPaymentDriver $rotessa;
|
||||
|
||||
public function __construct(RotessaPaymentDriver $rotessa)
|
||||
private array $transaction = [
|
||||
"financial_transactions" => [],
|
||||
"frequency" =>'Once',
|
||||
"installments" =>1
|
||||
];
|
||||
|
||||
public function __construct(protected RotessaPaymentDriver $rotessa)
|
||||
{
|
||||
$this->rotessa = $rotessa;
|
||||
$this->rotessa->init();
|
||||
}
|
||||
|
||||
@ -53,16 +50,13 @@ class PaymentMethod implements MethodInterface
|
||||
*/
|
||||
public function authorizeView(array $data): View
|
||||
{
|
||||
$data['contact'] = collect($data['client']->contacts->firstWhere('is_primary', 1)->toArray())->merge([
|
||||
$data['contact'] = collect($data['client']->contacts->first()->toArray())->merge([
|
||||
'home_phone' => $data['client']->phone,
|
||||
'custom_identifier' => $data['client']->number,
|
||||
'name' => $data['client']->name,
|
||||
'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,
|
||||
@ -78,41 +72,38 @@ class PaymentMethod implements MethodInterface
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function authorizeResponse(Request $request): RedirectResponse
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'gateway_type_id' => ['required','integer'],
|
||||
'country' => ['required'],
|
||||
'name' => ['required'],
|
||||
'address_1' => ['required'],
|
||||
'address_2' => ['required'],
|
||||
'city' => ['required'],
|
||||
'email' => ['required','email:filter'],
|
||||
'province_code' => ['required','size:2','alpha'],
|
||||
'postal_code' => ['required'],
|
||||
'authorization_type' => ['required'],
|
||||
'account_number' => ['required'],
|
||||
'bank_name' => ['required'],
|
||||
'phone' => ['required'],
|
||||
'home_phone' => ['required'],
|
||||
'bank_account_type'=>['required_if:country,US'],
|
||||
'routing_number'=>['required_if:country,US'],
|
||||
'institution_number'=>['required_if:country,CA','numeric'],
|
||||
'transit_number'=>['required_if:country,CA','numeric'],
|
||||
'custom_identifier'=>['required_without:customer_id'],
|
||||
'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'));
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return $this->rotessa->processInternallyFailedPayment($this->rotessa, new ClientPortalAuthorizationException( get_class( $e) . " : {$e->getMessage()}", (int) $e->getCode() ));
|
||||
}
|
||||
$request->validate([
|
||||
'gateway_type_id' => ['required','integer'],
|
||||
'country' => ['required','in:US,CA,United States,Canada'],
|
||||
'name' => ['required'],
|
||||
'address_1' => ['required'],
|
||||
'city' => ['required'],
|
||||
'email' => ['required','email:filter'],
|
||||
'province_code' => ['required','size:2','alpha'],
|
||||
'postal_code' => ['required'],
|
||||
'authorization_type' => ['required'],
|
||||
'account_number' => ['required'],
|
||||
'bank_name' => ['required'],
|
||||
'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','digits:3'],
|
||||
'transit_number'=>['required_if:country,CA','numeric','digits:5'],
|
||||
'custom_identifier'=>['required_without:customer_id'],
|
||||
'customer_id'=>['required_without:custom_identifier','integer'],
|
||||
'customer_type' => ['required', 'in:Personal,Business'],
|
||||
]);
|
||||
|
||||
$customer = array_merge(['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);
|
||||
|
||||
return redirect()->route('client.payment_methods.index')->withMessage(ctrans('texts.payment_method_added'));
|
||||
|
||||
return back()->withMessage(ctrans('texts.unable_to_verify_payment_method'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,7 +119,7 @@ class PaymentMethod implements MethodInterface
|
||||
$data['due_date'] = date('Y-m-d', min(max(strtotime($data['invoices']->max('due_date')), strtotime('now')), strtotime('+1 day')));
|
||||
$data['process_date'] = $data['due_date'];
|
||||
$data['currency'] = $this->rotessa->client->getCurrencyCode();
|
||||
$data['frequency'] = Frequencies::getOnePayment();
|
||||
$data['frequency'] = 'Once';
|
||||
$data['installments'] = 1;
|
||||
$data['invoice_nums'] = $data['invoices']->pluck('invoice_number')->join(', ');
|
||||
return render('gateways.rotessa.bank_transfer.pay', $data );
|
||||
@ -138,34 +129,41 @@ 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'],
|
||||
'amount' => ['required','numeric'],
|
||||
'process_date'=> ['required','date','after_or_equal:today'],
|
||||
]);
|
||||
|
||||
$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());
|
||||
$transaction = array_merge($this->transaction,[
|
||||
'amount' => $request->input('amount'),
|
||||
'process_date' => now()->addSeconds($customer->client->utc_offset())->format('Y-m-d'),
|
||||
'comment' => $this->rotessa->getDescription(false),
|
||||
'customer_id' => $customer->gateway_customer_reference,
|
||||
]);
|
||||
|
||||
$response = $this->rotessa->gatewayRequest('post','transaction_schedules', $transaction);
|
||||
|
||||
return $this->processPendingPayment($response->getParameter('id'), (float) $response->getParameter('amount'), (int) $customer->gateway_type_id , $customer->token);
|
||||
if($response->failed())
|
||||
$response->throw();
|
||||
|
||||
$response = $response->json();
|
||||
|
||||
return $this->processPendingPayment($response['id'], (float) $response['amount'], PaymentType::ACSS , $customer->token);
|
||||
} catch(\Throwable $e) {
|
||||
$this->processUnsuccessfulPayment( new InvalidResponseException($e->getMessage(), (int) $e->getCode()) );
|
||||
$this->processUnsuccessfulPayment( new \Exception($e->getMessage(), (int) $e->getCode()) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,7 +192,7 @@ class PaymentMethod implements MethodInterface
|
||||
/**
|
||||
* Handle unsuccessful payment for Rotessa.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @param \Exception $exception
|
||||
* @throws PaymentFailed
|
||||
* @return void
|
||||
*/
|
||||
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\PaymentDrivers\Rotessa\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Omnipay\Rotessa\Model\CustomerModel;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class Customer extends JsonResource
|
||||
{
|
||||
function __construct($resource) {
|
||||
parent::__construct( new CustomerModel($resource));
|
||||
}
|
||||
|
||||
function jsonSerialize() : array {
|
||||
return $this->resource->jsonSerialize();
|
||||
}
|
||||
|
||||
function toArray(Request $request) : array {
|
||||
return $this->additional + parent::toArray($request);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\PaymentDrivers\Rotessa\Resources;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Omnipay\Rotessa\Model\TransactionScheduleModel;
|
||||
|
||||
class Transaction extends JsonResource
|
||||
{
|
||||
function __construct($resource) {
|
||||
parent::__construct( new TransactionScheduleModel( $resource));
|
||||
}
|
||||
|
||||
function jsonSerialize() : array {
|
||||
return $this->resource->jsonSerialize();
|
||||
}
|
||||
|
||||
function toArray(Request $request) : array {
|
||||
return $this->additional + parent::toArray($request);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa;
|
||||
|
||||
use Omnipay\Common\AbstractGateway;
|
||||
use Omnipay\Rotessa\ClientInterface;
|
||||
use Omnipay\Rotessa\Message\RequestInterface;
|
||||
|
||||
abstract class AbstractClient extends AbstractGateway implements ClientInterface
|
||||
{
|
||||
|
||||
protected $default_parameters = [];
|
||||
|
||||
public function getDefaultParameters() : array {
|
||||
return $this->default_parameters;
|
||||
}
|
||||
|
||||
public function setDefaultParameters(array $params) {
|
||||
$this->default_parameters = $params;
|
||||
}
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa;
|
||||
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
trait ApiTrait
|
||||
{
|
||||
public function getCustomers() : RequestInterface {
|
||||
return $this->createRequest('GetCustomers', [] );
|
||||
}
|
||||
public function postCustomers(array $params) : RequestInterface {
|
||||
return $this->createRequest('PostCustomers', $params );
|
||||
}
|
||||
public function getCustomersId(array $params) : RequestInterface {
|
||||
return $this->createRequest('GetCustomersId', $params );
|
||||
}
|
||||
public function patchCustomersId(array $params) : RequestInterface {
|
||||
return $this->createRequest('PatchCustomersId', $params );
|
||||
}
|
||||
public function postCustomersShowWithCustomIdentifier(array $params) : RequestInterface {
|
||||
return $this->createRequest('PostCustomersShowWithCustomIdentifier', $params );
|
||||
}
|
||||
public function getTransactionSchedulesId(array $params) : RequestInterface {
|
||||
return $this->createRequest('GetTransactionSchedulesId', $params );
|
||||
}
|
||||
public function deleteTransactionSchedulesId(array $params) : RequestInterface {
|
||||
return $this->createRequest('DeleteTransactionSchedulesId', $params );
|
||||
}
|
||||
public function patchTransactionSchedulesId(array $params) : RequestInterface {
|
||||
return $this->createRequest('PatchTransactionSchedulesId', $params );
|
||||
}
|
||||
public function postTransactionSchedules(array $params) : RequestInterface {
|
||||
return $this->createRequest('PostTransactionSchedules', $params );
|
||||
}
|
||||
public function postTransactionSchedulesCreateWithCustomIdentifier(array $params) : RequestInterface {
|
||||
return $this->createRequest('PostTransactionSchedulesCreateWithCustomIdentifier', $params );
|
||||
}
|
||||
public function postTransactionSchedulesUpdateViaPost(array $params) : RequestInterface {
|
||||
return $this->createRequest('PostTransactionSchedulesUpdateViaPost', $params );
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa;
|
||||
|
||||
use Omnipay\Common\GatewayInterface;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
interface ClientInterface extends GatewayInterface
|
||||
{
|
||||
public function getDefaultParameters(): array;
|
||||
public function setDefaultParameters(array $data);
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Exception;
|
||||
|
||||
class BadRequestException extends \Exception {
|
||||
protected $message = "Your request includes invalid parameters";
|
||||
protected $code = 400;
|
||||
}
|
||||
|
||||
class UnauthorizedException extends \Exception {
|
||||
protected $message = "Your API key is not valid or is missing";
|
||||
protected $code = 401;
|
||||
}
|
||||
|
||||
class NotFoundException extends \Exception {
|
||||
protected $message = "The specified resource could not be found";
|
||||
protected $code = 404;
|
||||
}
|
||||
|
||||
class NotAcceptableException extends \Exception {
|
||||
protected $message = "You requested a format that isn’t json";
|
||||
protected $code = 406;
|
||||
}
|
||||
|
||||
class UnprocessableEntityException extends \Exception {
|
||||
protected $message = "Your request results in invalid data";
|
||||
protected $code = 422;
|
||||
}
|
||||
|
||||
class InternalServerErrorException extends \Exception {
|
||||
protected $message = "We had a problem with our server. Try again later";
|
||||
protected $code = 500;
|
||||
}
|
||||
|
||||
class ServiceUnavailableException extends \Exception {
|
||||
protected $message = "We're temporarily offline for maintenance. Please try again later";
|
||||
protected $code = 503;
|
||||
}
|
||||
|
||||
class ValidationException extends \Exception {
|
||||
protected $message = "A validation error has occured";
|
||||
protected $code = 600;
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa;
|
||||
|
||||
use Omnipay\Rotessa\ApiTrait;
|
||||
use Omnipay\Rotessa\AbstractClient;
|
||||
use Omnipay\Rotessa\ClientInterface;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class Gateway extends AbstractClient implements ClientInterface {
|
||||
|
||||
use ApiTrait;
|
||||
|
||||
protected $default_parameters = ['api_key' => 1234567890 ];
|
||||
|
||||
protected $test_mode = true;
|
||||
|
||||
protected $api_key;
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'Rotessa';
|
||||
}
|
||||
|
||||
public function getDefaultParameters() : array
|
||||
{
|
||||
return array_merge($this->default_parameters, array('api_key' => $this->api_key, 'test_mode' => $this->test_mode ) );
|
||||
}
|
||||
|
||||
public function setTestMode($value) {
|
||||
$this->test_mode = $value;
|
||||
}
|
||||
|
||||
public function getTestMode() {
|
||||
return $this->test_mode;
|
||||
}
|
||||
|
||||
protected function createRequest($class_name, ?array $parameters = [] ) :RequestInterface {
|
||||
$class = null;
|
||||
$class_name = "Omnipay\\Rotessa\\Message\\Request\\$class_name";
|
||||
$parameters = $class_name::hasModel() ? (($parameters = ($class_name::getModel($parameters)))->validate() ? $parameters->jsonSerialize() : null ) : $parameters;
|
||||
try {
|
||||
$class = new $class_name($this->httpClient, $this->httpRequest, $this->getDefaultParameters() + $parameters );
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
function setApiKey($value) {
|
||||
$this->api_key = $value;
|
||||
}
|
||||
|
||||
function getApiKey() {
|
||||
return $this->api_key;
|
||||
}
|
||||
|
||||
function authorize(array $options = []) : RequestInterface {
|
||||
return $this->postCustomers($options);
|
||||
}
|
||||
|
||||
function capture(array $options = []) : RequestInterface {
|
||||
return array_key_exists('customer_id', $options)? $this->postTransactionSchedules($options) : $this->postTransactionSchedulesCreateWithCustomIdentifier($options) ;
|
||||
}
|
||||
|
||||
function updateCustomer(array $options) : RequestInterface {
|
||||
return $this->patchCustomersId($options);
|
||||
}
|
||||
|
||||
function fetchTransaction($id = null) : RequestInterface {
|
||||
return $this->getTransactionSchedulesId(compact('id'));
|
||||
}
|
||||
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Http;
|
||||
|
||||
use Omnipay\Common\Http\Client as HttpClient;
|
||||
use Omnipay\Common\Http\Exception\NetworkException;
|
||||
use Omnipay\Common\Http\Exception\RequestException;
|
||||
use Http\Discovery\HttpClientDiscovery;
|
||||
use Http\Discovery\MessageFactoryDiscovery;
|
||||
use Http\Message\RequestFactory;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class Client extends HttpClient
|
||||
{
|
||||
/**
|
||||
* The Http Client which implements `public function sendRequest(RequestInterface $request)`
|
||||
* Note: Will be changed to PSR-18 when released
|
||||
*
|
||||
* @var HttpClient
|
||||
*/
|
||||
private $httpClient;
|
||||
|
||||
/**
|
||||
* @var RequestFactory
|
||||
*/
|
||||
private $requestFactory;
|
||||
|
||||
public function __construct($httpClient = null, RequestFactory $requestFactory = null)
|
||||
{
|
||||
$this->httpClient = $httpClient ?: HttpClientDiscovery::find();
|
||||
$this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find();
|
||||
parent::__construct($httpClient, $requestFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $method
|
||||
* @param $uri
|
||||
* @param array $headers
|
||||
* @param string|array|resource|StreamInterface|null $body
|
||||
* @param string $protocolVersion
|
||||
* @return ResponseInterface
|
||||
* @throws \Http\Client\Exception
|
||||
*/
|
||||
public function request(
|
||||
$method,
|
||||
$uri,
|
||||
array $headers = [],
|
||||
$body = null,
|
||||
$protocolVersion = '1.1'
|
||||
) {
|
||||
return $this->sendRequest($method, $uri, $headers, $body, $protocolVersion);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws \Http\Client\Exception
|
||||
*/
|
||||
private function sendRequest( $method,
|
||||
$uri,
|
||||
array $headers = [],
|
||||
$body = null,
|
||||
$protocolVersion = '1.1')
|
||||
{
|
||||
|
||||
$response = null;
|
||||
|
||||
try {
|
||||
if( method_exists($this->httpClient, 'sendRequest'))
|
||||
$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);
|
||||
} catch (\Exception $exception) {
|
||||
throw new RequestException($exception->getMessage(), $request, $exception);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Http\Response;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
class Response extends JsonResponse
|
||||
{
|
||||
|
||||
protected $reason_phrase = '';
|
||||
protected $reason_code = '';
|
||||
|
||||
public function __construct(mixed $data = null, int $status = 200, array $headers = [])
|
||||
{
|
||||
|
||||
parent::__construct($data , $status, $headers, true);
|
||||
|
||||
if(array_key_exists('errors',$data = json_decode( $this->content, true) )) {
|
||||
$data = $data['errors'][0];
|
||||
$this->reason_phrase = $data['error_message'] ;
|
||||
$this->reason_code = $data['error_message'] ;
|
||||
}
|
||||
}
|
||||
|
||||
public function getReasonPhrase() {
|
||||
return $this->reason_phrase;
|
||||
}
|
||||
|
||||
public function getReasonCode() {
|
||||
return $this->reason_code;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa;
|
||||
|
||||
trait IsValidTypeTrait {
|
||||
|
||||
public static function isValid(string $value) {
|
||||
return in_array($value, self::getTypes());
|
||||
}
|
||||
|
||||
abstract public static function getTypes() : array;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
use Omnipay\Common\Message\AbstractRequest as Request;
|
||||
use Omnipay\Rotessa\Message\Response\ResponseInterface;
|
||||
|
||||
abstract class AbstractRequest extends Request implements RequestInterface
|
||||
{
|
||||
|
||||
protected $test_mode = false;
|
||||
protected $api_version;
|
||||
protected $method = 'GET';
|
||||
protected $endpoint;
|
||||
protected $api_key;
|
||||
|
||||
public function setApiKey(string $value) {
|
||||
$this->api_key = $value;
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
try {
|
||||
if(empty($this->api_key)) throw new \Exception('No Api Key Found!');
|
||||
$this->validate( ...array_keys($data = $this->getParameters()));
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Omnipay\Rotessa\Exception\ValidationException($th->getMessage() , 600, $th);
|
||||
}
|
||||
|
||||
return (array) $data;
|
||||
}
|
||||
|
||||
abstract public function sendData($data) : ResponseInterface;
|
||||
|
||||
abstract protected function sendRequest(string $method, string $endpoint, array $headers = [], array $data = [] );
|
||||
|
||||
abstract protected function createResponse(array $data) : ResponseInterface;
|
||||
|
||||
abstract public function getEndpointUrl(): string;
|
||||
|
||||
public function getEndpoint() : string {
|
||||
return $this->endpoint;
|
||||
}
|
||||
|
||||
public function getTestMode() {
|
||||
return $this->test_mode;
|
||||
}
|
||||
|
||||
public function setTestMode($mode) {
|
||||
$this->test_mode = $mode;
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
|
||||
use Omnipay\Common\Http\ClientInterface;
|
||||
use Omnipay\Rotessa\Http\Response\Response;
|
||||
use Omnipay\Rotessa\Message\Response\BaseResponse;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
use Omnipay\Rotessa\Message\Response\ResponseInterface;
|
||||
use Symfony\Component\HttpFoundation\Request as HttpRequest;
|
||||
|
||||
class BaseRequest extends AbstractRequest implements RequestInterface
|
||||
{
|
||||
protected $base_url = 'rotessa.com';
|
||||
protected $api_version = 1;
|
||||
protected $endpoint = '';
|
||||
|
||||
const ENVIRONMENT_SANDBOX = 'sandbox-api';
|
||||
const ENVIRONMENT_LIVE = 'api';
|
||||
|
||||
function __construct(ClientInterface $http_client = null, HttpRequest $http_request, $model ) {
|
||||
parent::__construct($http_client, $http_request );
|
||||
$this->initialize($model);
|
||||
}
|
||||
|
||||
protected function sendRequest(string $method, string $endpoint, array $headers = [], array $data = [])
|
||||
{
|
||||
/**
|
||||
* @param $method
|
||||
* @param $uri
|
||||
* @param array $headers
|
||||
* @param string|resource|StreamInterface|null $body
|
||||
* @param string $protocolVersion
|
||||
* @return ResponseInterface
|
||||
* @throws \Http\Client\Exception
|
||||
*/
|
||||
$response = $this->httpClient->request($method, $endpoint, $headers, json_encode($data) ) ;
|
||||
$this->response = new Response ($response->getBody()->getContents(), $response->getStatusCode(), $response->getHeaders(), true);
|
||||
}
|
||||
|
||||
|
||||
protected function createResponse(array $data): ResponseInterface {
|
||||
|
||||
return new BaseResponse($this, $data, $this->response->getStatusCode(), $this->response->getReasonPhrase());
|
||||
}
|
||||
|
||||
protected function replacePlaceholder($string, $array) {
|
||||
$pattern = "/\{([^}]+)\}/";
|
||||
$replacement = function($matches) use($array) {
|
||||
$key = $matches[1];
|
||||
if (array_key_exists($key, $array)) {
|
||||
return $array[$key];
|
||||
} else {
|
||||
return $matches[0];
|
||||
}
|
||||
};
|
||||
|
||||
return preg_replace_callback($pattern, $replacement, $string);
|
||||
}
|
||||
|
||||
public function sendData($data) :ResponseInterface {
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
'Authorization' => "Token token={$this->api_key}"
|
||||
];
|
||||
|
||||
$this->sendRequest(
|
||||
$this->method,
|
||||
$this->getEndpointUrl(),
|
||||
$headers,
|
||||
$data);
|
||||
|
||||
return $this->createResponse(json_decode($this->response->getContent(), true));
|
||||
}
|
||||
|
||||
public function getEndpoint() : string {
|
||||
return $this->replacePlaceholder($this->endpoint, $this->getParameters());
|
||||
}
|
||||
|
||||
public function getEndpointUrl() : string {
|
||||
return sprintf('https://%s.%s/v%d%s',$this->test_mode ? self::ENVIRONMENT_SANDBOX : self::ENVIRONMENT_LIVE ,$this->base_url, $this->api_version, $this->getEndpoint());
|
||||
}
|
||||
|
||||
public static function hasModel() : bool {
|
||||
return (bool) static::$model;
|
||||
}
|
||||
|
||||
public static function getModel($parameters = []) {
|
||||
$class_name = static::$model;
|
||||
$class_name = "Omnipay\\Rotessa\\Model\\{$class_name}Model";
|
||||
return new $class_name($parameters);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class DeleteTransactionSchedulesId extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/transaction_schedules/{id}';
|
||||
protected $method = 'DELETE';
|
||||
protected static $model = '';
|
||||
|
||||
public function setId(string $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class GetCustomers extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/customers';
|
||||
protected $method = 'GET';
|
||||
protected static $model = '';
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class GetCustomersId extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/customers/{id}';
|
||||
protected $method = 'GET';
|
||||
protected static $model = '';
|
||||
|
||||
|
||||
|
||||
public function setId(int $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class GetTransactionSchedulesId extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/transaction_schedules/{id}';
|
||||
protected $method = 'GET';
|
||||
protected static $model = '';
|
||||
|
||||
public function setId(int $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class PatchCustomersId extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/customers/{id}';
|
||||
protected $method = 'PATCH';
|
||||
protected static $model = 'CustomerPatch';
|
||||
|
||||
public function setId(string $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
public function setCustomIdentifier(string $value) {
|
||||
$this->setParameter('custom_identifier',$value);
|
||||
}
|
||||
public function setName(string $value) {
|
||||
$this->setParameter('name',$value);
|
||||
}
|
||||
public function setEmail(string $value) {
|
||||
$this->setParameter('email',$value);
|
||||
}
|
||||
public function setCustomerType(string $value) {
|
||||
$this->setParameter('customer_type',$value);
|
||||
}
|
||||
public function setHomePhone(string $value) {
|
||||
$this->setParameter('home_phone',$value);
|
||||
}
|
||||
public function setPhone(string $value) {
|
||||
$this->setParameter('phone',$value);
|
||||
}
|
||||
public function setBankName(string $value) {
|
||||
$this->setParameter('bank_name',$value);
|
||||
}
|
||||
public function setInstitutionNumber(string $value) {
|
||||
$this->setParameter('institution_number',$value);
|
||||
}
|
||||
public function setTransitNumber(string $value) {
|
||||
$this->setParameter('transit_number',$value);
|
||||
}
|
||||
public function setBankAccountType(string $value) {
|
||||
$this->setParameter('bank_account_type',$value);
|
||||
}
|
||||
public function setAuthorizationType(string $value) {
|
||||
$this->setParameter('authorization_type',$value);
|
||||
}
|
||||
public function setRoutingNumber(string $value) {
|
||||
$this->setParameter('routing_number',$value);
|
||||
}
|
||||
public function setAccountNumber(string $value) {
|
||||
$this->setParameter('account_number',$value);
|
||||
}
|
||||
public function setAddress(array $value) {
|
||||
$this->setParameter('address',$value);
|
||||
}
|
||||
public function setTransactionSchedules(array $value) {
|
||||
$this->setParameter('transaction_schedules',$value);
|
||||
}
|
||||
public function setFinancialTransactions(array $value) {
|
||||
$this->setParameter('financial_transactions',$value);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class PatchTransactionSchedulesId extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/transaction_schedules/{id}';
|
||||
protected $method = 'PATCH';
|
||||
|
||||
public function setId(int $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
public function setAmount(int $value) {
|
||||
$this->setParameter('amount',$value);
|
||||
}
|
||||
public function setComment(string $value) {
|
||||
$this->setParameter('comment',$value);
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class PostCustomers extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/customers';
|
||||
protected $method = 'POST';
|
||||
protected static $model = 'Customer';
|
||||
|
||||
|
||||
public function setId(string $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
public function setCustomIdentifier(string $value) {
|
||||
$this->setParameter('custom_identifier',$value);
|
||||
}
|
||||
public function setName(string $value) {
|
||||
$this->setParameter('name',$value);
|
||||
}
|
||||
public function setEmail(string $value) {
|
||||
$this->setParameter('email',$value);
|
||||
}
|
||||
public function setCustomerType(string $value) {
|
||||
$this->setParameter('customer_type',$value);
|
||||
}
|
||||
public function setHomePhone(string $value) {
|
||||
$this->setParameter('home_phone',$value);
|
||||
}
|
||||
public function setPhone(string $value) {
|
||||
$this->setParameter('phone',$value);
|
||||
}
|
||||
public function setBankName(string $value) {
|
||||
$this->setParameter('bank_name',$value);
|
||||
}
|
||||
public function setInstitutionNumber(string $value = '') {
|
||||
$this->setParameter('institution_number',$value);
|
||||
}
|
||||
public function setTransitNumber(string $value = '') {
|
||||
$this->setParameter('transit_number',$value);
|
||||
}
|
||||
public function setBankAccountType(string $value) {
|
||||
$this->setParameter('bank_account_type',$value);
|
||||
}
|
||||
public function setAuthorizationType(string $value = '') {
|
||||
$this->setParameter('authorization_type',$value);
|
||||
}
|
||||
public function setRoutingNumber(string $value = '') {
|
||||
$this->setParameter('routing_number',$value);
|
||||
}
|
||||
public function setAccountNumber(string $value) {
|
||||
$this->setParameter('account_number',$value);
|
||||
}
|
||||
public function setAddress(array $value) {
|
||||
$this->setParameter('address',$value);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class PostCustomersShowWithCustomIdentifier extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/customers/show_with_custom_identifier';
|
||||
protected $method = 'POST';
|
||||
protected static $model = null;
|
||||
|
||||
|
||||
public function setCustomIdentifier(string $value) {
|
||||
$this->setParameter('custom_identifier',$value);
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class PostTransactionSchedules extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/transaction_schedules';
|
||||
protected $method = 'POST';
|
||||
protected static $model = 'TransactionSchedule';
|
||||
|
||||
|
||||
|
||||
public function setCustomerId(string $value) {
|
||||
$this->setParameter('customer_id',$value);
|
||||
}
|
||||
public function setProcessDate(string $value) {
|
||||
$this->setParameter('process_date',$value);
|
||||
}
|
||||
public function setFrequency(string $value) {
|
||||
$this->setParameter('frequency',$value);
|
||||
}
|
||||
public function setInstallments(int $value) {
|
||||
$this->setParameter('installments',$value);
|
||||
}
|
||||
public function setComment(string $value) {
|
||||
$this->setParameter('comment',$value);
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class PostTransactionSchedulesCreateWithCustomIdentifier extends PostTransactionSchedules implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/transaction_schedules/create_with_custom_identifier';
|
||||
protected $method = 'POST';
|
||||
|
||||
public function setCustomIdentifier(string $value) {
|
||||
$this->setParameter('custom_identifier',$value);
|
||||
}
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
|
||||
use Omnipay\Rotessa\Message\Request\BaseRequest;
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
|
||||
class PostTransactionSchedulesUpdateViaPost extends BaseRequest implements RequestInterface
|
||||
{
|
||||
|
||||
protected $endpoint = '/transaction_schedules/update_via_post';
|
||||
protected $method = 'POST';
|
||||
|
||||
|
||||
|
||||
public function setId(int $value) {
|
||||
$this->setParameter('id',$value);
|
||||
}
|
||||
public function setAmount(int $value) {
|
||||
$this->setParameter('amount',$value);
|
||||
}
|
||||
public function setComment(string $value) {
|
||||
$this->setParameter('comment',$value);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Request;
|
||||
|
||||
use Omnipay\Rotessa\Message\Response\ResponseInterface;
|
||||
use Omnipay\Common\Message\RequestInterface as MessageInterface;
|
||||
|
||||
interface RequestInterface extends MessageInterface
|
||||
{
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Response;
|
||||
|
||||
use Omnipay\Common\Message\AbstractResponse as Response;
|
||||
|
||||
abstract class AbstractResponse extends Response implements ResponseInterface
|
||||
{
|
||||
|
||||
abstract public function getData();
|
||||
|
||||
abstract public function getCode();
|
||||
|
||||
abstract public function getMessage();
|
||||
|
||||
abstract public function getParameter(string $key);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Response;
|
||||
|
||||
use Omnipay\Rotessa\Message\Request\RequestInterface;
|
||||
use Omnipay\Rotessa\Message\Response\ResponseInterface;
|
||||
use Omnipay\Common\Message\AbstractResponse as Response;
|
||||
|
||||
class BaseResponse extends Response implements ResponseInterface
|
||||
{
|
||||
|
||||
protected $code = 0;
|
||||
protected $message = null;
|
||||
|
||||
function __construct(RequestInterface $request, array $data = [], int $code = 200, string $message = null ) {
|
||||
parent::__construct($request, $data);
|
||||
|
||||
$this->code = $code;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->getParameters();
|
||||
}
|
||||
|
||||
public function getCode() {
|
||||
return (int) $this->code;
|
||||
}
|
||||
|
||||
public function isSuccessful() {
|
||||
return $this->code < 300;
|
||||
}
|
||||
|
||||
public function getMessage() {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
protected function getParameters() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function getParameter(string $key) {
|
||||
return $this->getParameters()[$key];
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Message\Response;
|
||||
|
||||
use Omnipay\Common\Message\ResponseInterface as MessageInterface;
|
||||
|
||||
interface ResponseInterface extends MessageInterface
|
||||
{
|
||||
public function getParameter(string $key) ;
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Model;
|
||||
|
||||
use Omnipay\Common\ParametersTrait;
|
||||
use Omnipay\Rotessa\Model\ModelInterface;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Omnipay\Rotessa\Exception\ValidationException;
|
||||
|
||||
abstract class AbstractModel implements ModelInterface {
|
||||
|
||||
use ParametersTrait;
|
||||
|
||||
abstract public function jsonSerialize() : array;
|
||||
|
||||
public function validate() : bool {
|
||||
$required = array_diff_key( array_flip($this->required), array_filter($this->getParameters()) );
|
||||
if(!empty($required)) throw new ValidationException("Could not validate " . implode(",", array_keys($required)) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
return array_key_exists($key, $this->attributes) ? $this->getParameter($key) : null;
|
||||
}
|
||||
|
||||
public function __set($key, $value) {
|
||||
if(array_key_exists($key, $this->attributes)) $this->setParameter($key, $value);
|
||||
}
|
||||
|
||||
public function __toString() : string {
|
||||
return json_encode($this);
|
||||
}
|
||||
|
||||
public function toString() : string {
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
public function __toArray() : array {
|
||||
return $this->getParameters();
|
||||
}
|
||||
|
||||
|
||||
public function toArray() : array {
|
||||
return $this->__toArray();
|
||||
}
|
||||
|
||||
public function initialize(array $params = []) {
|
||||
$this->parameters = new ParameterBag;
|
||||
$parameters = array_merge($this->defaults, $params);
|
||||
if ($parameters) {
|
||||
foreach ($this->attributes as $param => $type) {
|
||||
$value = @$parameters[$param];
|
||||
if($value){
|
||||
settype($value, $type);
|
||||
$this->setParameter($param, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Model;
|
||||
|
||||
use \DateTime;
|
||||
use Omnipay\Rotessa\Model\AbstractModel;
|
||||
use Omnipay\Rotessa\Model\ModelInterface;
|
||||
|
||||
class BaseModel extends AbstractModel implements ModelInterface {
|
||||
|
||||
protected $attributes = [
|
||||
"id" => "string"
|
||||
];
|
||||
protected $required = ['id'];
|
||||
protected $defaults = ['id' => 0 ];
|
||||
|
||||
public function __construct($parameters = array()) {
|
||||
$this->initialize($parameters);
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array {
|
||||
return array_intersect_key($this->toArray(), array_flip($this->required) );
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Model;
|
||||
|
||||
use Omnipay\Rotessa\Object\Country;
|
||||
use Omnipay\Rotessa\Object\Address;
|
||||
use Omnipay\Rotessa\Model\BaseModel;
|
||||
use Omnipay\Rotessa\Object\CustomerType;
|
||||
use Omnipay\Rotessa\Model\ModelInterface;
|
||||
use Omnipay\Rotessa\Object\BankAccountType;
|
||||
use Omnipay\Rotessa\Object\AuthorizationType;
|
||||
use Omnipay\Rotessa\Exception\ValidationException;
|
||||
|
||||
class CustomerModel extends BaseModel implements ModelInterface {
|
||||
|
||||
protected $attributes = [
|
||||
"id" => "string",
|
||||
"custom_identifier" => "string",
|
||||
"name" => "string",
|
||||
"email" => "string",
|
||||
"customer_type" => "string",
|
||||
"home_phone" => "string",
|
||||
"phone" => "string",
|
||||
"bank_name" => "string",
|
||||
"institution_number" => "string",
|
||||
"transit_number" => "string",
|
||||
"bank_account_type" => "string",
|
||||
"authorization_type" => "string",
|
||||
"routing_number" => "string",
|
||||
"account_number" => "string",
|
||||
"address" => "object",
|
||||
"transaction_schedules" => "array",
|
||||
"financial_transactions" => "array",
|
||||
"active" => "bool"
|
||||
];
|
||||
|
||||
protected $defaults = ["active" => false,"customer_type" =>'Business',"bank_account_type" =>'Savings',"authorization_type" =>'Online',];
|
||||
protected $required = ["name","email","customer_type","home_phone","phone","bank_name","institution_number","transit_number","bank_account_type","authorization_type","routing_number","account_number","address",'custom_identifier'];
|
||||
|
||||
public function validate() : bool {
|
||||
try {
|
||||
$country = $this->address->country;
|
||||
if(!self::isValidCountry($country)) throw new \Exception("Invalid country!");
|
||||
|
||||
$this->required = array_diff($this->required, Country::isAmerican($country) ? ["institution_number", "transit_number"] : ["bank_account_type", "routing_number"]);
|
||||
parent::validate();
|
||||
if(Country::isCanadian($country) ) {
|
||||
if(!self::isValidTransitNumber($this->getParameter('transit_number'))) throw new \Exception("Invalid transit number!");
|
||||
if(!self::isValidInstitutionNumber($this->getParameter('institution_number'))) throw new \Exception("Invalid institution number!");
|
||||
}
|
||||
if(!self::isValidCustomerType($this->getParameter('customer_type'))) throw new \Exception("Invalid customer type!");
|
||||
if(!self::isValidBankAccountType($this->getParameter('bank_account_type'))) throw new \Exception("Invalid bank account type!");
|
||||
if(!self::isValidAuthorizationType($this->getParameter('authorization_type'))) throw new \Exception("Invalid authorization type!");
|
||||
} catch (\Throwable $th) {
|
||||
throw new ValidationException($th->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isValidCountry(string $country ) : bool {
|
||||
return Country::isValidCountryCode($country) || Country::isValidCountryName($country);
|
||||
}
|
||||
|
||||
public static function isValidTransitNumber(string $value ) : bool {
|
||||
return strlen($value) == 5;
|
||||
}
|
||||
|
||||
public static function isValidInstitutionNumber(string $value ) : bool {
|
||||
return strlen($value) == 3;
|
||||
}
|
||||
|
||||
public static function isValidCustomerType(string $value ) : bool {
|
||||
return CustomerType::isValid($value);
|
||||
}
|
||||
|
||||
public static function isValidBankAccountType(string $value ) : bool {
|
||||
return BankAccountType::isValid($value);
|
||||
}
|
||||
|
||||
public static function isValidAuthorizationType(string $value ) : bool {
|
||||
return AuthorizationType::isValid($value);
|
||||
}
|
||||
|
||||
public function toArray() : array {
|
||||
return [ 'address' => (array) $this->getParameter('address') ] + parent::toArray();
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array {
|
||||
$address = (array) $this->getParameter('address');
|
||||
unset($address['country']);
|
||||
|
||||
return compact('address') + parent::jsonSerialize();
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Model;
|
||||
|
||||
use Omnipay\Rotessa\Object\Country;
|
||||
use Omnipay\Rotessa\Object\Address;
|
||||
use Omnipay\Rotessa\Object\CustomerType;
|
||||
use Omnipay\Rotessa\Model\ModelInterface;
|
||||
use Omnipay\Rotessa\Object\BankAccountType;
|
||||
use Omnipay\Rotessa\Object\AuthorizationType;
|
||||
use Omnipay\Rotessa\Exception\ValidationException;
|
||||
|
||||
class CustomerPatchModel extends CustomerModel implements ModelInterface {
|
||||
|
||||
protected $required = ["id","custom_identifier","name","email","customer_type","home_phone","phone","bank_name","institution_number","transit_number","bank_account_type","authorization_type","routing_number","account_number","address"];
|
||||
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Model;
|
||||
|
||||
interface ModelInterface extends \JsonSerializable
|
||||
{
|
||||
public function __toArray();
|
||||
public function __toString();
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Model;
|
||||
|
||||
use \DateTime;
|
||||
use Omnipay\Rotessa\Model\BaseModel;
|
||||
use Omnipay\Rotessa\Object\Frequency;
|
||||
use Omnipay\Rotessa\Model\ModelInterface;
|
||||
use Omnipay\Rotessa\Exception\ValidationException;
|
||||
|
||||
class TransactionScheduleModel extends BaseModel implements ModelInterface {
|
||||
|
||||
protected $properties;
|
||||
|
||||
protected $attributes = [
|
||||
"id" => "string",
|
||||
"amount" => "float",
|
||||
"comment" => "string",
|
||||
"created_at" => "date",
|
||||
"financial_transactions" => "array",
|
||||
"frequency" => "string",
|
||||
"installments" => "integer",
|
||||
"next_process_date" => "date",
|
||||
"process_date" => "date",
|
||||
"updated_at" => "date",
|
||||
"customer_id" => "string",
|
||||
"custom_identifier" => "string",
|
||||
];
|
||||
|
||||
public const DATE_FORMAT = 'F j, Y';
|
||||
|
||||
protected $defaults = ["amount" =>0.00,"comment" =>' ',"financial_transactions" =>0,"frequency" =>'Once',"installments" =>1];
|
||||
|
||||
protected $required = ["amount","comment","frequency","installments","process_date"];
|
||||
|
||||
public function validate() : bool {
|
||||
try {
|
||||
parent::validate();
|
||||
if(!self::isValidDate($this->process_date)) throw new \Exception("Could not validate date ");
|
||||
if(!self::isValidFrequency($this->frequency)) throw new \Exception("Invalid frequency");
|
||||
if(is_null($this->customer_id) && is_null($this->custom_identifier)) throw new \Exception("customer id or custom identifier is invalid");
|
||||
} catch (\Throwable $th) {
|
||||
throw new ValidationException($th->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array {
|
||||
return ['customer_id' => $this->getParameter('customer_id'), 'custom_identifier' => $this->getParameter('custom_identifier') ] + parent::jsonSerialize() ;
|
||||
}
|
||||
|
||||
public function __toArray() : array {
|
||||
return parent::__toArray() ;
|
||||
}
|
||||
|
||||
public function initialize(array $params = [] ) {
|
||||
$o_params = array_intersect_key(
|
||||
$params = array_intersect_key($params, $this->attributes),
|
||||
($attr = array_filter($this->attributes, fn($p) => $p != "date"))
|
||||
);
|
||||
parent::initialize($o_params);
|
||||
$d_params = array_diff_key($params, $attr);
|
||||
array_walk($d_params, function($v,$k) {
|
||||
$this->setParameter($k, self::formatDate( $v) );
|
||||
}, );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function isValidDate($date) : bool {
|
||||
$d = DateTime::createFromFormat(self::DATE_FORMAT, $date);
|
||||
// Check if the date is valid and matches the format
|
||||
return $d && $d->format(self::DATE_FORMAT) === $date;
|
||||
}
|
||||
|
||||
public static function isValidFrequency($value) : bool {
|
||||
return Frequency::isValid($value);
|
||||
}
|
||||
|
||||
protected static function formatDate($date) : string {
|
||||
$d = new DateTime($date);
|
||||
return $d->format(self::DATE_FORMAT);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Model;
|
||||
|
||||
use Omnipay\Rotessa\Model\BaseModel;
|
||||
use Omnipay\Rotessa\Model\ModelInterface;
|
||||
|
||||
class TransactionSchedulesIdBodyModel extends BaseModel implements ModelInterface {
|
||||
|
||||
protected $properties;
|
||||
|
||||
protected $attributes = [
|
||||
"amount" => "int",
|
||||
"comment" => "string",
|
||||
];
|
||||
|
||||
public const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
private $_is_error = false;
|
||||
|
||||
protected $defaults = ["amount" =>0,"comment" =>'0',];
|
||||
|
||||
protected $required = ["amount","comment",];
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Model;
|
||||
|
||||
use Omnipay\Rotessa\Model\BaseModel;
|
||||
use Omnipay\Rotessa\Model\ModelInterface;
|
||||
|
||||
class TransactionSchedulesUpdateViaPostBodyModel extends BaseModel implements ModelInterface {
|
||||
|
||||
protected $properties;
|
||||
|
||||
protected $attributes = [
|
||||
"id" => "int",
|
||||
"amount" => "int",
|
||||
"comment" => "string",
|
||||
];
|
||||
|
||||
public const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
private $_is_error = false;
|
||||
|
||||
protected $defaults = ["amount" =>0,"comment" =>'0',];
|
||||
|
||||
protected $required = ["amount","comment",];
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
namespace Omnipay\Rotessa\Object;
|
||||
|
||||
use Omnipay\Common\ParametersTrait;
|
||||
|
||||
final class Address implements \JsonSerializable {
|
||||
|
||||
use ParametersTrait;
|
||||
|
||||
protected $attributes = [
|
||||
"address_1" => "string",
|
||||
"address_2" => "string",
|
||||
"city" => "string",
|
||||
"id" => "int",
|
||||
"postal_code" => "string",
|
||||
"province_code" => "string",
|
||||
"country" => "string"
|
||||
];
|
||||
|
||||
protected $required = ["address_1","address_2","city","postal_code","province_code",];
|
||||
|
||||
public function jsonSerialize() {
|
||||
return array_intersect_key($this->getParameters(), array_flip($this->required));
|
||||
}
|
||||
|
||||
public function getCountry() : string {
|
||||
return $this->getParameter('country');
|
||||
}
|
||||
|
||||
public function initialize(array $parameters) {
|
||||
foreach($this->attributes as $param => $type) {
|
||||
$value = @$parameters[$param] ;
|
||||
settype($value, $type);
|
||||
$value = $value ?? null;
|
||||
$this->parameters->set($param, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function __toArray() : array {
|
||||
return $this->getParameters();
|
||||
}
|
||||
|
||||
public function __toString() : string {
|
||||
return $this->getFullAddress();
|
||||
}
|
||||
|
||||
public function getFullAddress() :string {
|
||||
$full_address = $this->getParameters();
|
||||
extract($full_address);
|
||||
|
||||
return "$address_1 $address_2, $city, $postal_code $province_code, $country";
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Object;
|
||||
|
||||
use Omnipay\Rotessa\IsValidTypeTrait;
|
||||
|
||||
final class AuthorizationType {
|
||||
|
||||
use isValidTypeTrait;
|
||||
|
||||
const IN_PERSON = "In Person";
|
||||
const ONLINE = "Online";
|
||||
|
||||
public static function isInPerson($value) {
|
||||
return $value === self::IN_PERSON;
|
||||
}
|
||||
|
||||
public static function isOnline($value) {
|
||||
return $value === self::ONLINE;
|
||||
}
|
||||
|
||||
public static function getTypes() : array {
|
||||
return [
|
||||
self::IN_PERSON,
|
||||
self::ONLINE
|
||||
];
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Object;
|
||||
|
||||
use Omnipay\Rotessa\IsValidTypeTrait;
|
||||
|
||||
final class BankAccountType {
|
||||
|
||||
use IsValidTypeTrait;
|
||||
|
||||
const SAVINGS = "Savings";
|
||||
const CHECKING = "Checking";
|
||||
|
||||
public static function isSavings($value) {
|
||||
return $value === self::SAVINGS;
|
||||
}
|
||||
|
||||
public static function isChecking($value) {
|
||||
return $value === self::Checking;
|
||||
}
|
||||
|
||||
public static function getTypes() : array {
|
||||
return [
|
||||
self::SAVINGS,
|
||||
self::CHECKING
|
||||
];
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Object;
|
||||
|
||||
use Omnipay\Rotessa\IsValidTypeTrait;
|
||||
|
||||
final class Country {
|
||||
|
||||
use IsValidTypeTrait;
|
||||
|
||||
protected static $codes = ['CA','US'];
|
||||
protected static $names = ['United States', 'Canada'];
|
||||
|
||||
public static function isValidCountryName(string $value) {
|
||||
return in_array($value, self::$names);
|
||||
}
|
||||
|
||||
public static function isValidCountryCode(string $value) {
|
||||
return in_array($value, self::$codes);
|
||||
}
|
||||
|
||||
public static function isAmerican(string $value) : bool {
|
||||
return $value == 'US' || $value == 'United States';
|
||||
}
|
||||
|
||||
public static function isCanadian(string $value) : bool {
|
||||
return $value == 'CA' || $value == 'Canada';
|
||||
}
|
||||
|
||||
public static function getTypes() : array {
|
||||
return $codes + $names;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Object;
|
||||
|
||||
use Omnipay\Rotessa\IsValidTypeTrait;
|
||||
|
||||
final class CustomerType {
|
||||
|
||||
use IsValidTypeTrait;
|
||||
|
||||
const PERSONAL = "Personal";
|
||||
const BUSINESS = "Business";
|
||||
|
||||
public static function isPersonal($value) {
|
||||
return $value === self::PERSONAL;
|
||||
}
|
||||
|
||||
public static function isBusiness($value) {
|
||||
return $value === self::BUSINESS;
|
||||
}
|
||||
|
||||
public static function getTypes() : array {
|
||||
return [
|
||||
self::PERSONAL,
|
||||
self::BUSINESS
|
||||
];
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Omnipay\Rotessa\Object;
|
||||
|
||||
use Omnipay\Rotessa\IsValidTypeTrait;
|
||||
|
||||
final class Frequency {
|
||||
|
||||
use IsValidTypeTrait;
|
||||
|
||||
const ONCE = "Once";
|
||||
const WEEKLY = "Weekly";
|
||||
const OTHER_WEEK = "Every Other Week";
|
||||
const MONTHLY= "Monthly";
|
||||
const OTHER_MONTH = "Every Other Month";
|
||||
const QUARTERLY = "Quarterly";
|
||||
const SEMI_ANNUALLY = "Semi-Annually";
|
||||
const YEARLY = "Yearly";
|
||||
|
||||
public static function isOnce($value) {
|
||||
return $value === self::ONCE;
|
||||
}
|
||||
|
||||
public static function isWeekly($value) {
|
||||
return $value === self::WEEKLY;
|
||||
}
|
||||
|
||||
public static function isOtherWeek($value) {
|
||||
return $value === self::OTHER_WEEK;
|
||||
}
|
||||
|
||||
public static function isMonthly($value) {
|
||||
return $value === self::MONTHLY;
|
||||
}
|
||||
|
||||
public static function isOtherMonth($value) {
|
||||
return $value === self::OTHER_MONTH;
|
||||
}
|
||||
|
||||
public static function isQuarterly($value) {
|
||||
return $value === self::QUARTERLY;
|
||||
}
|
||||
|
||||
public static function isSemiAnnually($value) {
|
||||
return $value === self::SEMI_ANNUALLY;
|
||||
}
|
||||
|
||||
public static function isYearly($value) {
|
||||
return $value === self::YEARLY;
|
||||
}
|
||||
|
||||
public static function getTypes() : array {
|
||||
return [
|
||||
self::ONCE,
|
||||
self::WEEKLY,
|
||||
self::OTHER_WEEK,
|
||||
self::MONTHLY,
|
||||
self::OTHER_MONTH,
|
||||
self::QUARTERLY,
|
||||
self::SEMI_ANNUALLY,
|
||||
self::YEARLY
|
||||
];
|
||||
}
|
||||
}
|
@ -11,24 +11,19 @@
|
||||
|
||||
namespace App\PaymentDrivers;
|
||||
|
||||
use Omnipay\Omnipay;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\Models\Client;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\PaymentHash;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Models\GatewayType;
|
||||
use Omnipay\Rotessa\Gateway;
|
||||
use App\Models\ClientContact;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\PaymentDrivers\BaseDriver;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
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
|
||||
{
|
||||
@ -40,24 +35,15 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
|
||||
public $can_authorise_credit_card = true;
|
||||
|
||||
public Gateway $gateway;
|
||||
|
||||
public $payment_method;
|
||||
|
||||
public static $methods = [
|
||||
GatewayType::BANK_TRANSFER => BankTransfer::class,
|
||||
//GatewayType::BACS => Bacs::class,
|
||||
GatewayType::ACSS => Acss::class,
|
||||
// GatewayType::DIRECT_DEBIT => DirectDebit::class
|
||||
];
|
||||
|
||||
public function init(): self
|
||||
{
|
||||
|
||||
$this->gateway = Omnipay::create(
|
||||
$this->company_gateway->gateway->provider
|
||||
);
|
||||
$this->gateway->initialize((array) $this->company_gateway->getConfig());
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -116,73 +102,61 @@ 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',[]); //Rotessa customers
|
||||
|
||||
if($result->failed())
|
||||
$result->throw();
|
||||
|
||||
$customers = collect($result->json())->unique('email'); //Rotessa customer emails
|
||||
|
||||
$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();
|
||||
$this->client = Client::find($contact->client_id);
|
||||
$customer = (new Customer($result->getData()))->additional(['id' => $contact->id, 'custom_identifier' => $contact->custom_identifier ] );
|
||||
$this->findOrCreateCustomer($customer->additional + $customer->jsonSerialize());
|
||||
collect($client_contacts)->each(
|
||||
function($contact) {
|
||||
|
||||
$contact = (object)$contact;
|
||||
|
||||
$result = $this->gatewayRequest("get","customers/{$contact->id}");
|
||||
$result = $result->json();
|
||||
|
||||
$this->client = Client::query()->find($contact->client_id);
|
||||
|
||||
$customer = array_merge($result, ['id' => $contact->id, 'custom_identifier' => $contact->custom_identifier ]);
|
||||
|
||||
$this->findOrCreateCustomer($customer);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
// create new clients from rotessa customers
|
||||
$client_emails = $client_contacts->pluck('email')->all();
|
||||
|
||||
$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();
|
||||
/**
|
||||
{
|
||||
"account_number": "11111111"
|
||||
"active": true,
|
||||
"address": {
|
||||
"address_1": "123 Main Street",
|
||||
"address_2": "Unit 4",
|
||||
"city": "Birmingham",
|
||||
"id": 114397,
|
||||
"postal_code": "36016",
|
||||
"province_code": "AL"
|
||||
},
|
||||
"authorization_type": "Online",
|
||||
"bank_account_type": "Checking",
|
||||
"bank_name": "Scotiabank",
|
||||
"created_at": "2015-02-10T23:50:45.000-06:00",
|
||||
"custom_identifier": "Mikey",
|
||||
"customer_type": "Personal",
|
||||
"email": "mikesmith@test.com",
|
||||
"financial_transactions": [],
|
||||
"home_phone": "(204) 555 5555",
|
||||
"id": 1,
|
||||
"identifier": "Mikey",
|
||||
"institution_number": "",
|
||||
"name": "Mike Smith",
|
||||
"phone": "(204) 555 4444",
|
||||
"routing_number": "111111111",
|
||||
"transaction_schedules": [],
|
||||
"transit_number": "",
|
||||
"updated_at": "2015-02-10T23:50:45.000-06:00"
|
||||
}
|
||||
*/
|
||||
|
||||
$customer = $this->gatewayRequest("get", "customers/{$customer['id']}")->json();
|
||||
|
||||
$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 +166,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();
|
||||
@ -207,8 +182,7 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
$client->contacts()->saveMany([$contact]);
|
||||
$contact = $client->contacts()->first();
|
||||
$this->client = $client;
|
||||
$customer = (new Customer((array) $customer))->additional(['id' => $customer->id, 'custom_identifier' => $customer->custom_identifier ?? $contact->id ] );
|
||||
$this->findOrCreateCustomer($customer->additional + $customer->jsonSerialize());
|
||||
|
||||
});
|
||||
} catch (\Throwable $th) {
|
||||
$data = [
|
||||
@ -228,40 +202,46 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
|
||||
public function findOrCreateCustomer(array $data)
|
||||
{
|
||||
nlog($data);
|
||||
|
||||
$result = null;
|
||||
try {
|
||||
|
||||
$existing = ClientGatewayToken::query()
|
||||
->where('company_gateway_id', $this->company_gateway->id)
|
||||
->where('client_id', $this->client->id)
|
||||
->orWhere(function (Builder $query) use ($data) {
|
||||
$query->where('token', encrypt(join(".", Arr::only($data, 'id','custom_identifier'))) )
|
||||
->where('gateway_customer_reference', Arr::only($data,'id'));
|
||||
})
|
||||
->where('is_deleted',0)
|
||||
->where('gateway_customer_reference', Arr::only($data,'id'))
|
||||
->exists();
|
||||
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());
|
||||
|
||||
$customer = new Customer($result->getData());
|
||||
$data = array_filter($customer->resolve());
|
||||
if ($existing)
|
||||
return true;
|
||||
|
||||
if(!isset($data['id'])) {
|
||||
|
||||
nlog("no id, lets goo");
|
||||
$result = $this->gatewayRequest('post', 'customers', $data);
|
||||
|
||||
if($result->failed())
|
||||
$result->throw();
|
||||
|
||||
$data = $result->json();
|
||||
nlog($data);
|
||||
}
|
||||
|
||||
// $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' => ['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']
|
||||
, 'routing_number' => Arr::has($data,'routing_number') ? $data['routing_number'] : $data['transit_number'] ]);
|
||||
], [
|
||||
'gateway_customer_reference' => $data['id'],
|
||||
'routing_number' => Arr::has($data,'routing_number') ? $data['routing_number'] : $data['transit_number']
|
||||
]);
|
||||
|
||||
return $data['id'];
|
||||
|
||||
throw new \Exception($result->getMessage(), (int) $result->getCode());
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$data = [
|
||||
@ -269,7 +249,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 +257,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/';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -59,6 +59,11 @@ class ChargeRefunded implements ShouldQueue
|
||||
|
||||
$payment_hash_key = $source['metadata']['payment_hash'] ?? null;
|
||||
|
||||
if(is_null($payment_hash_key)){
|
||||
nlog("charge.refunded not found");
|
||||
return;
|
||||
}
|
||||
|
||||
$payment_hash = PaymentHash::query()->where('hash', $payment_hash_key)->first();
|
||||
$company_gateway = $payment_hash->payment->company_gateway;
|
||||
|
||||
|
@ -59,38 +59,22 @@ class PaymentIntentProcessingWebhook implements ShouldQueue
|
||||
/* Stub processing payment intents with a pending payment */
|
||||
public function handle()
|
||||
{
|
||||
nlog($this->stripe_request);
|
||||
// The first payment will always be a PI payment - subsequent are PY
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
$company = Company::query()->where('company_key', $this->company_key)->first();
|
||||
|
||||
foreach ($this->stripe_request as $transaction) {
|
||||
|
||||
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $company->id)
|
||||
->where(function ($query) use ($transaction) {
|
||||
|
||||
if(isset($transaction['payment_intent'])) {
|
||||
$query->where('transaction_reference', $transaction['payment_intent']);
|
||||
}
|
||||
|
||||
if(isset($transaction['payment_intent']) && isset($transaction['id'])) {
|
||||
$query->orWhere('transaction_reference', $transaction['id']);
|
||||
}
|
||||
|
||||
if(!isset($transaction['payment_intent']) && isset($transaction['id'])) {
|
||||
$query->where('transaction_reference', $transaction['id']);
|
||||
}
|
||||
|
||||
})
|
||||
->where('transaction_reference', $transaction['id'])
|
||||
->first();
|
||||
|
||||
|
||||
|
||||
if ($payment) {
|
||||
$payment->status_id = Payment::STATUS_PENDING;
|
||||
$payment->save();
|
||||
|
||||
nlog("found payment");
|
||||
$this->payment_completed = true;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,6 @@ class ComposerServiceProvider extends ServiceProvider
|
||||
$view->with('states', $states);
|
||||
});
|
||||
|
||||
// CAProvinces View Composer
|
||||
view()->composer(['*.rotessa.components.address','*.rotessa.components.banks.CA.bank','*.rotessa.components.dropdowns.country.CA'], function ($view) {
|
||||
$provinces = CAProvinces::get();
|
||||
$view->with('provinces', $provinces);
|
||||
|
@ -11,17 +11,17 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Http\Middleware\ThrottleRequestsWithPredis;
|
||||
use App\Models\Scheduler;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Scheduler;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use App\Http\Middleware\ThrottleRequestsWithPredis;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,9 +11,12 @@
|
||||
|
||||
namespace App\Services\Chart;
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* Class ChartCalculations.
|
||||
@ -170,4 +173,215 @@ trait ChartCalculations
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
public function getLoggedTasks($data): int|float
|
||||
{
|
||||
|
||||
$q = $this->taskQuery($data);
|
||||
|
||||
return $this->taskCalculations($q, $data);
|
||||
|
||||
}
|
||||
|
||||
public function getPaidTasks($data): int|float
|
||||
{
|
||||
$q = $this->taskQuery($data);
|
||||
$q->whereHas('invoice', function ($query){
|
||||
$query->where('status_id', 4)->where('is_deleted', 0);
|
||||
});
|
||||
|
||||
return $this->taskCalculations($q, $data);
|
||||
|
||||
}
|
||||
|
||||
public function getInvoicedTasks($data): int|float
|
||||
{
|
||||
|
||||
$q = $this->taskQuery($data);
|
||||
$q->whereHas('invoice');
|
||||
|
||||
return $this->taskCalculations($q, $data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* All Expenses
|
||||
*/
|
||||
public function getLoggedExpenses($data): int|float
|
||||
{
|
||||
$q = $this->expenseQuery($data);
|
||||
|
||||
return $this->expenseCalculations($q, $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expenses that should be invoiced - but are not yet invoiced.
|
||||
*/
|
||||
public function getPendingExpenses($data): int|float
|
||||
{
|
||||
|
||||
$q = $this->expenseQuery($data);
|
||||
$q->where('should_be_invoiced', true)->whereNull('invoice_id');
|
||||
return $this->expenseCalculations($q, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoiced.
|
||||
*/
|
||||
public function getInvoicedExpenses($data): int|float
|
||||
{
|
||||
|
||||
$q = $this->expenseQuery($data);
|
||||
$q->whereNotNull('invoice_id');
|
||||
return $this->expenseCalculations($q, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paid.
|
||||
*/
|
||||
public function getPaidExpenses($data): int|float
|
||||
{
|
||||
|
||||
$q = $this->expenseQuery($data);
|
||||
$q->whereNotNull('payment_date');
|
||||
return $this->expenseCalculations($q, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paid.
|
||||
*/
|
||||
public function getInvoicedPaidExpenses($data): int|float
|
||||
{
|
||||
|
||||
$q = $this->expenseQuery($data);
|
||||
$q->whereNotNull('invoice_id')->whereNotNull('payment_date');
|
||||
return $this->expenseCalculations($q, $data);
|
||||
}
|
||||
|
||||
private function expenseCalculations(Builder $query, array $data): int|float
|
||||
{
|
||||
|
||||
$result = 0;
|
||||
$calculated = $this->expenseCalculator($query, $data);
|
||||
|
||||
match ($data['calculation']) {
|
||||
'sum' => $result = $calculated->sum(),
|
||||
'avg' => $result = $calculated->avg(),
|
||||
'count' => $result = $query->count(),
|
||||
default => $result = 0,
|
||||
};
|
||||
|
||||
return $result;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function expenseCalculator(Builder $query, array $data)
|
||||
{
|
||||
|
||||
return $query->get()
|
||||
->when($data['currency_id'] == '999', function ($collection) {
|
||||
$collection->map(function ($e) {
|
||||
/** @var \App\Models\Expense $e */
|
||||
return $e->amount * $e->exchange_rate;
|
||||
});
|
||||
})
|
||||
->when($data['currency_id'] != '999', function ($collection) {
|
||||
|
||||
$collection->map(function ($e) {
|
||||
|
||||
/** @var \App\Models\Expense $e */
|
||||
return $e->amount;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function expenseQuery($data): Builder
|
||||
{
|
||||
$query = Expense::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', 0);
|
||||
|
||||
if(in_array($data['period'], ['current,previous'])) {
|
||||
$query->whereBetween('date', [$data['start_date'], $data['end_date']]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
private function taskMoneyCalculator($query, $data)
|
||||
{
|
||||
|
||||
return $query->get()
|
||||
->when($data['currency_id'] == '999', function ($collection) {
|
||||
$collection->map(function ($t) {
|
||||
return $t->taskCompanyValue();
|
||||
});
|
||||
})
|
||||
->when($data['currency_id'] != '999', function ($collection) {
|
||||
|
||||
$collection->map(function ($t) {
|
||||
return $t->taskValue();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function taskQuery($data): Builder
|
||||
{
|
||||
$q = Task::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', 0);
|
||||
|
||||
if(in_array($data['period'], ['current,previous'])) {
|
||||
$q->whereBetween('calculated_start_date', [$data['start_date'], $data['end_date']]);
|
||||
}
|
||||
|
||||
return $q;
|
||||
|
||||
}
|
||||
|
||||
private function taskCalculations(Builder $q, array $data): int|float
|
||||
{
|
||||
|
||||
$result = 0;
|
||||
$calculated = collect();
|
||||
|
||||
if($data['calculation'] != 'count' && $data['format'] == 'money') {
|
||||
if($data['currency_id'] != '999') {
|
||||
|
||||
$q->whereHas('client', function ($query) use ($data) {
|
||||
$query->where('settings->currency_id', $data['currency_id']);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
$calculated = $this->taskMoneyCalculator($q, $data);
|
||||
|
||||
}
|
||||
|
||||
if($data['calculation'] != 'count' && $data['format'] == 'time') {
|
||||
$calculated = $q->get()->map(function ($t) {
|
||||
return $t->calcDuration();
|
||||
});
|
||||
}
|
||||
|
||||
match ($data['calculation']) {
|
||||
'sum' => $result = $calculated->sum(),
|
||||
'avg' => $result = $calculated->avg(),
|
||||
'count' => $result = $q->count(),
|
||||
default => $result = 0,
|
||||
};
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -224,6 +224,8 @@ class ChartService
|
||||
* period - current/previous
|
||||
* calculation - sum/count/avg
|
||||
*
|
||||
* May require currency_id
|
||||
*
|
||||
* date_range - this_month
|
||||
* or
|
||||
* start_date - end_date
|
||||
@ -234,18 +236,18 @@ class ChartService
|
||||
|
||||
match($data['field']){
|
||||
'active_invoices' => $results = $this->getActiveInvoices($data),
|
||||
'outstanding_invoices' => $results = 0,
|
||||
'completed_payments' => $results = 0,
|
||||
'refunded_payments' => $results = 0,
|
||||
'active_quotes' => $results = 0,
|
||||
'unapproved_quotes' => $results = 0,
|
||||
'logged_tasks' => $results = 0,
|
||||
'invoiced_tasks' => $results = 0,
|
||||
'paid_tasks' => $results = 0,
|
||||
'logged_expenses' => $results = 0,
|
||||
'pending_expenses' => $results = 0,
|
||||
'invoiced_expenses' => $results = 0,
|
||||
'invoice_paid_expenses' => $results = 0,
|
||||
'outstanding_invoices' => $results = $this->getOutstandingInvoices($data),
|
||||
'completed_payments' => $results = $this->getCompletedPayments($data),
|
||||
'refunded_payments' => $results = $this->getRefundedPayments($data),
|
||||
'active_quotes' => $results = $this->getActiveQuotes($data),
|
||||
'unapproved_quotes' => $results = $this->getUnapprovedQuotes($data),
|
||||
'logged_tasks' => $results = $this->getLoggedTasks($data),
|
||||
'invoiced_tasks' => $results = $this->getInvoicedTasks($data),
|
||||
'paid_tasks' => $results = $this->getPaidTasks($data),
|
||||
'logged_expenses' => $results = $this->getLoggedExpenses($data),
|
||||
'pending_expenses' => $results = $this->getPendingExpenses($data),
|
||||
'invoiced_expenses' => $results = $this->getInvoicedExpenses($data),
|
||||
'invoice_paid_expenses' => $results = $this->getInvoicedPaidExpenses($data),
|
||||
default => $results = 0,
|
||||
};
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -124,7 +124,7 @@ class TemplateService
|
||||
$this->twig->addFilter($filter);
|
||||
|
||||
$allowedTags = ['if', 'for', 'set', 'filter'];
|
||||
$allowedFilters = ['escape', 'e', 'upper', 'lower', 'capitalize', 'filter', 'length', 'merge','format_currency', 'format_number','format_percent_number','map', 'join', 'first', 'date', 'sum', 'number_format'];
|
||||
$allowedFilters = ['replace', 'escape', 'e', 'upper', 'lower', 'capitalize', 'filter', 'length', 'merge','format_currency', 'format_number','format_percent_number','map', 'join', 'first', 'date', 'sum', 'number_format','nl2br'];
|
||||
$allowedFunctions = ['range', 'cycle', 'constant', 'date',];
|
||||
$allowedProperties = ['type_id'];
|
||||
$allowedMethods = ['img','t'];
|
||||
@ -323,6 +323,9 @@ class TemplateService
|
||||
$template = $template->render($this->data);
|
||||
|
||||
$f = $this->document->createDocumentFragment();
|
||||
|
||||
$template = htmlspecialchars($template, ENT_XML1, 'UTF-8');
|
||||
|
||||
$f->appendXML(html_entity_decode($template));
|
||||
|
||||
$replacements[] = $f;
|
||||
@ -482,6 +485,8 @@ class TemplateService
|
||||
default => $processed = [],
|
||||
};
|
||||
|
||||
// nlog(json_encode($processed));
|
||||
|
||||
return $processed;
|
||||
|
||||
})->toArray();
|
||||
|
@ -17,77 +17,67 @@ use Illuminate\Support\Str;
|
||||
|
||||
class TranslationHelper
|
||||
{
|
||||
public static function getIndustries()
|
||||
{
|
||||
// public static function getIndustries()
|
||||
// {
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
|
||||
$industries = app('industries');
|
||||
// /** @var \Illuminate\Support\Collection<\App\Models\Currency> */
|
||||
// $industries = app('industries');
|
||||
|
||||
return $industries->each(function ($industry) {
|
||||
$industry->name = ctrans('texts.industry_'.$industry->name);
|
||||
})->sortBy(function ($industry) {
|
||||
return $industry->name;
|
||||
});
|
||||
}
|
||||
// return $industries->each(function ($industry) {
|
||||
// $industry->name = ctrans('texts.industry_'.$industry->name);
|
||||
// })->sortBy(function ($industry) {
|
||||
// return $industry->name;
|
||||
// });
|
||||
// }
|
||||
|
||||
public static function getCountries()
|
||||
{
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Country> */
|
||||
// $countries = app('countries');
|
||||
return app('countries');
|
||||
|
||||
return \App\Models\Country::all()->each(function ($country) {
|
||||
$country->name = ctrans('texts.country_'.$country->name);
|
||||
})->sortBy(function ($country) {
|
||||
return $country->iso_3166_2;
|
||||
});
|
||||
}
|
||||
|
||||
public static function getPaymentTypes()
|
||||
{
|
||||
// public static function getPaymentTypes()
|
||||
// {
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\PaymentType> */
|
||||
// $payment_types = app('payment_types');
|
||||
// /** @var \Illuminate\Support\Collection<\App\Models\PaymentType> */
|
||||
// // $payment_types = app('payment_types');
|
||||
|
||||
return \App\Models\PaymentType::all()->each(function ($pType) {
|
||||
$pType->name = ctrans('texts.payment_type_'.$pType->name);
|
||||
})->sortBy(function ($pType) {
|
||||
return $pType->name;
|
||||
});
|
||||
}
|
||||
// return \App\Models\PaymentType::all()->each(function ($pType) {
|
||||
// $pType->name = ctrans('texts.payment_type_'.$pType->name);
|
||||
// })->sortBy(function ($pType) {
|
||||
// return $pType->name;
|
||||
// });
|
||||
// }
|
||||
|
||||
public static function getLanguages()
|
||||
{
|
||||
// public static function getLanguages()
|
||||
// {
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Language> */
|
||||
// $languages = app('languages');
|
||||
// /** @var \Illuminate\Support\Collection<\App\Models\Language> */
|
||||
// // $languages = app('languages');
|
||||
|
||||
return \App\Models\Language::all()->each(function ($lang) {
|
||||
$lang->name = ctrans('texts.lang_'.$lang->name);
|
||||
})->sortBy(function ($lang) {
|
||||
return $lang->name;
|
||||
});
|
||||
}
|
||||
// return \App\Models\Language::all()->each(function ($lang) {
|
||||
// $lang->name = ctrans('texts.lang_'.$lang->name);
|
||||
// })->sortBy(function ($lang) {
|
||||
// return $lang->name;
|
||||
// });
|
||||
// }
|
||||
|
||||
public static function getCurrencies()
|
||||
{
|
||||
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
|
||||
// $currencies = app('currencies');
|
||||
return app('currencies');
|
||||
|
||||
return \App\Models\Currency::all()->each(function ($currency) {
|
||||
$currency->name = ctrans('texts.currency_'.Str::slug($currency->name, '_'));
|
||||
})->sortBy(function ($currency) {
|
||||
return $currency->name;
|
||||
});
|
||||
}
|
||||
|
||||
public static function getPaymentTerms()
|
||||
{
|
||||
return PaymentTerm::getCompanyTerms()->map(function ($term) {
|
||||
$term['name'] = ctrans('texts.payment_terms_net').' '.$term['num_days'];
|
||||
// public static function getPaymentTerms()
|
||||
// {
|
||||
// return PaymentTerm::getCompanyTerms()->map(function ($term) {
|
||||
// $term['name'] = ctrans('texts.payment_terms_net').' '.$term['num_days'];
|
||||
|
||||
return $term;
|
||||
});
|
||||
}
|
||||
// return $term;
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
@ -74,14 +74,12 @@
|
||||
"league/csv": "^9.6",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"league/fractal": "^0.20.0",
|
||||
"league/omnipay": "^3.1",
|
||||
"livewire/livewire": "^3",
|
||||
"microsoft/microsoft-graph": "^1.69",
|
||||
"mollie/mollie-api-php": "^2.36",
|
||||
"nelexa/zip": "^4.0",
|
||||
"nordigen/nordigen-php": "^1.1",
|
||||
"nwidart/laravel-modules": "^11.0",
|
||||
"omnipay/paypal": "^3.0",
|
||||
"phpoffice/phpspreadsheet": "^1.29",
|
||||
"pragmarx/google2fa": "^8.0",
|
||||
"predis/predis": "^2",
|
||||
@ -133,7 +131,8 @@
|
||||
"app/Helpers/Generic.php",
|
||||
"app/Helpers/ClientPortal.php"
|
||||
],
|
||||
"classmap": ["app/PaymentDrivers/Rotessa/vendor/karneaud/omnipay-rotessa/src/Omnipay/Rotessa/"]
|
||||
"classmap": [
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
@ -186,7 +185,7 @@
|
||||
"url": "https://github.com/turbo124/apple"
|
||||
},
|
||||
{
|
||||
"type":"vcs",
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/invoiceninja/einvoice"
|
||||
},
|
||||
{
|
||||
@ -204,4 +203,4 @@
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
833
composer.lock
generated
833
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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.19'),
|
||||
'app_tag' => env('APP_TAG', '5.10.19'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
@ -12,18 +12,18 @@ return new class extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Company::whereNotNull('tax_data')
|
||||
->cursor()
|
||||
->each(function($company){
|
||||
// Company::whereNotNull('tax_data')
|
||||
// ->cursor()
|
||||
// ->each(function($company){
|
||||
|
||||
if($company->tax_data?->version == 'alpha')
|
||||
{
|
||||
// if($company->tax_data?->version == 'alpha' && ($company->tax_data->seller_subregion ?? false))
|
||||
// {
|
||||
|
||||
$company->update(['tax_data' => new \App\DataMapper\Tax\TaxModel($company->tax_data)]);
|
||||
// $company->update(['tax_data' => new \App\DataMapper\Tax\TaxModel($company->tax_data)]);
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
});
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if(Ninja::isSelfHost())
|
||||
{
|
||||
|
||||
Company::whereNotNull('tax_data')
|
||||
->cursor()
|
||||
->each(function ($company) {
|
||||
|
||||
if($company->tax_data?->version == 'alpha' && ($company->tax_data->seller_subregion ?? false)) {
|
||||
|
||||
$company->update(['tax_data' => new \App\DataMapper\Tax\TaxModel($company->tax_data)]);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -5309,7 +5309,8 @@ $lang = array(
|
||||
'account_holder_information' => 'Account Holder Information',
|
||||
'enter_information_for_the_account_holder' => 'Enter Information for the Account Holder',
|
||||
'customer_type' => 'Customer Type',
|
||||
'process_date' => 'Process Date'
|
||||
'process_date' => 'Process Date',
|
||||
'forever_free' => 'Forever Free',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
1425
lang/nl/texts.php
1425
lang/nl/texts.php
File diff suppressed because it is too large
Load Diff
13780
openapi/api-docs.yaml
13780
openapi/api-docs.yaml
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user