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

Set Invitations as a default include for invoices (#3362)

* Working on importing company gateways

* Fix for companyuser settings object

* Migrate client_gateway_tokens

* Working on Notificaitons

* Working on notifications

* Failsafe for user-company

* unlink files

* Set DB for jobs

* Always have a fallback for company_id

* Fixes for user model

* Formatting for MultiDB

* Working on Company Ledger Tests

* Fixes for contact request

* Set Invitations as a default include for invoices
This commit is contained in:
David Bomba 2020-02-24 21:15:30 +11:00 committed by GitHub
parent c97d664d31
commit 3d31f810c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 810 additions and 371 deletions

View File

@ -83,6 +83,14 @@ class CreateTestData extends Command
'account_id' => $account->id,
]);
$settings = $company->settings;
$settings->system_notifications_slack = config('ninja.notification.slack');
$settings->system_notifications_email = config('ninja.contact.email');
$company->settings = $settings;
$company->save();
$account->default_company_id = $company->id;
$account->save();
@ -167,6 +175,15 @@ class CreateTestData extends Command
'account_id' => $account->id,
]);
$settings = $company->settings;
$settings->system_notifications_slack = config('ninja.notification.slack');
$settings->system_notifications_email = config('ninja.contact.email');
$company->settings = $settings;
$company->save();
$account->default_company_id = $company->id;
$account->save();
@ -265,6 +282,14 @@ class CreateTestData extends Command
'account_id' => $account->id,
]);
$settings = $company->settings;
$settings->system_notifications_slack = config('ninja.notification.slack');
$settings->system_notifications_email = config('ninja.contact.email');
$company->settings = $settings;
$company->save();
$account->default_company_id = $company->id;
$account->save();

View File

@ -193,6 +193,9 @@ class CompanySettings extends BaseSettings {
public $client_online_payment_notification = true;
public $client_manual_payment_notification = true;
public $system_notifications_slack = '';
public $system_notifications_email = '';
/* Company Meta data that we can use to build sub companies*/
public $name = '';
@ -220,6 +223,8 @@ class CompanySettings extends BaseSettings {
public $pdf_variables = [];
public static $casts = [
'system_notifications_slack' => 'string',
'system_notifications_email' => 'string',
'portal_design_id' => 'string',
'late_fee_endless_percent' => 'float',
'late_fee_endless_amount' => 'float',
@ -401,9 +406,11 @@ class CompanySettings extends BaseSettings {
* @return object
*/
public static function defaults():\stdClass {
$config = json_decode(config('ninja.settings'));
$data = (object) get_class_vars(CompanySettings::class );
unset($data->casts);
unset($data->protected_fields);
@ -415,7 +422,7 @@ class CompanySettings extends BaseSettings {
$data->date_format_id = (string) config('ninja.i18n.date_format_id');
$data->country_id = (string) config('ninja.i18n.country_id');
$data->translations = (object) [];
$data->pdf_variables = (array) self::getEntityVariableDefaults();
$data->pdf_variables = (array) self::getEntityVariableDefaults();
// $data->email_subject_invoice = EmailTemplateDefaults::emailInvoiceSubject();
// $data->email_template_invoice = EmailTemplateDefaults:: emailInvoiceTemplate();

View File

@ -213,8 +213,8 @@ class CompanyController extends BaseController
'is_owner' => 1,
'is_admin' => 1,
'is_locked' => 0,
'permissions' => json_encode([]),
'settings' => json_encode(DefaultSettings::userSettings()),
'permissions' => '',
'settings' => DefaultSettings::userSettings(),
]);
/*

View File

@ -520,7 +520,7 @@ class InvoiceController extends BaseController {
});
ZipInvoices::dispatch($invoices, $invoices->first()->company);
ZipInvoices::dispatch($invoices, $invoices->first()->company, auth()->user()->email);
return response()->json(['message' => 'Email Sent!'],200);
}

View File

@ -28,8 +28,8 @@ class TokenAuth
public function handle($request, Closure $next)
{
if ($request->header('X-API-TOKEN') && ($company_token = CompanyToken::with(['user','company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first())) {
$user = $company_token->user;
$error = [
'message' => 'User inactive',
@ -48,6 +48,12 @@ class TokenAuth
|
*/
$user->setCompany($company_token->company);
config(['ninja.company_id' => $company_token->company->id]);
app('queue')->createPayloadUsing(function () use($company_token) {
return ['db' => $company_token->company->db];
});
//user who once existed, but has been soft deleted
if ($user->company_user->is_locked) {

View File

@ -70,12 +70,12 @@ class StoreClientRequest extends Request
if(isset($input['contacts']))
{
foreach($input['contacts'] as $contact)
foreach($input['contacts'] as $key => $contact)
{
if(is_numeric($contact['id']))
unset($contact['id']);
unset($input['contacts'][$key]['id']);
elseif(is_string($contact['id']))
$contact['id'] = $this->decodePrimaryKey($contact['id']);
$input['contacts'][$key]['id'] = $this->decodePrimaryKey($contact['id']);
}
}

View File

@ -81,12 +81,12 @@ class UpdateClientRequest extends Request
if(isset($input['contacts']))
{
foreach($input['contacts'] as $contact)
foreach($input['contacts'] as $key => $contact)
{
if(is_numeric($contact['id']))
unset($contact['id']);
unset($input['contacts'][$key]['id']);
elseif(is_string($contact['id']))
$contact['id'] = $this->decodePrimaryKey($contact['id']);
$input['contacts'][$key]['id'] = $this->decodePrimaryKey($contact['id']);
}
}

View File

@ -92,8 +92,14 @@ class CreateAccount
$user->fresh();
Notification::route('slack', config('ninja.notification.slack'))
->notify(new NewAccountCreated($user, $company));
$company->notification(new NewAccountCreated($user, $company))->run();
// $user->route('slack', $company->settings->system_notifications_slack)
// ->route('mail', $company->settings->system_notifications_email)
// ->notify(new NewAccountCreated($user, $company));
// Notification::route('slack', config('ninja.notification.slack'))
// ->notify(new NewAccountCreated($user, $company));
return $account;
}

View File

@ -35,19 +35,17 @@ class ApplyCreditPayment implements ShouldQueue
public $amount;
private $company;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Credit $credit, Payment $payment, float $amount, Company $company)
public function __construct(Credit $credit, Payment $payment, float $amount)
{
$this->credit = $credit;
$this->payment = $payment;
$this->amount = $amount;
$this->company = $company;
}
/**
@ -58,7 +56,6 @@ class ApplyCreditPayment implements ShouldQueue
*/
public function handle()
{
MultiDB::setDB($this->company->db);
/* Update Pivot Record amount */
$this->payment->credits->each(function ($cred) {

View File

@ -16,7 +16,6 @@ use App\Events\Credit\CreditWasEmailedAndFailed;
use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
use App\Mail\TemplateEmail;
use App\Models\Company;
use App\Models\Credit;
use App\Models\SystemLog;
use Illuminate\Bus\Queueable;
@ -34,18 +33,15 @@ class EmailCredit implements ShouldQueue
public $message_array = [];
private $company;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Credit $credit, Company $company)
public function __construct(Credit $credit)
{
$this->credit = $credit;
$this->company = $company;
}
/**
@ -56,9 +52,6 @@ class EmailCredit implements ShouldQueue
*/
public function handle()
{
/*Jobs are not multi-db aware, need to set! */
MultiDB::setDB($this->company->db);
//todo - change runtime config of mail driver if necessary
$template_style = $this->credit->client->getSetting('email_style');

View File

@ -21,20 +21,16 @@ class StoreCredit implements ShouldQueue
protected $data;
private $company;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Credit $credit, array $data, Company $company)
public function __construct(Credit $credit, array $data)
{
$this->credit = $credit;
$this->data = $data;
$this->company = $company;
}
/**

View File

@ -12,6 +12,7 @@
namespace App\Jobs\Cron;
use App\Jobs\RecurringInvoice\SendRecurring;
use App\Libraries\MultiDB;
use App\Models\RecurringInvoice;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;

View File

@ -63,8 +63,6 @@ class CreateInvoicePdf implements ShouldQueue {
public function handle() {
MultiDB::setDB($this->company->db);
$this->invoice->load('client');
if(!$this->contact)

View File

@ -54,7 +54,6 @@ class EmailInvoice implements ShouldQueue
public function handle()
{
MultiDB::setDb($this->company->db);
Mail::to($this->invoice_invitation->contact->email, $this->invoice_invitation->contact->present()->name())
->send(new TemplateEmail($this->email_builder,

View File

@ -64,8 +64,6 @@ class StoreInvoice implements ShouldQueue
*/
public function handle(InvoiceRepository $invoice_repo) : ?Invoice
{
MultiDB::setDB($this->company->db);
$payment = false;
// /* Test if we should auto-bill the invoice */

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Invoice;
use App\Jobs\Util\UnlinkFile;
use App\Libraries\MultiDB;
use App\Mail\DownloadInvoices;
use App\Models\Company;
@ -20,10 +21,10 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
use Illuminate\Support\Facades\Mail;
class ZipInvoices implements ShouldQueue
{
@ -33,17 +34,21 @@ class ZipInvoices implements ShouldQueue
private $company;
private $email;
/**
* @deprecated confirm to be deleted
* Create a new job instance.
*
* @return void
*/
public function __construct($invoices, Company $company)
public function __construct($invoices, Company $company, $email)
{
$this->invoices = $invoices;
$this->company = $company;
$this->email = $email;
}
/**
@ -54,7 +59,6 @@ class ZipInvoices implements ShouldQueue
*/
public function handle()
{
MultiDB::setDB($this->company->db);
$tempStream = fopen('php://memory', 'w+');
@ -78,9 +82,9 @@ class ZipInvoices implements ShouldQueue
fclose($tempStream);
//fire email here
Mail::to(config('ninja.contact.ninja_official_contact'))
->send(new DownloadInvoices(Storage::disk(config('filesystems.default'))->url($path . $file_name)));
Mail::to($this->email)
->send(new DownloadInvoices(Storage::disk(config('filesystems.default'))->url($path . $file_name), $this->company));
UnlinkFile::dispatch(config('filesystems.default'), $path . $file_name)->delay(now()->addHours(1));
}
}

View File

@ -48,7 +48,6 @@ class PaymentNotification implements ShouldQueue
*/
public function handle()
{
MultiDB::setDB($this->company->db);
//notification for the payment.
//

View File

@ -15,12 +15,17 @@ use App\Factory\RecurringInvoiceToInvoiceFactory;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Http\Request;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Foundation\Bus\Dispatchable;
class SendRecurring
class SendRecurring implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use GeneratesCounter;
public $recurring_invoice;
@ -46,7 +51,6 @@ class SendRecurring
*/
public function handle() : void
{
MultiDb::setDb($this->db);
// Generate Standard Invoice
$invoice = RecurringInvoiceToInvoiceFactory::create($this->recurring_invoice);

View File

@ -14,12 +14,15 @@ use App\Factory\QuoteFactory;
use App\Factory\TaxRateFactory;
use App\Factory\UserFactory;
use App\Http\Requests\Company\UpdateCompanyRequest;
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
use App\Http\ValidationRules\ValidUserForCompany;
use App\Jobs\Company\CreateCompanyToken;
use App\Libraries\MultiDB;
use App\Mail\MigrationFailed;
use App\Models\Client;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\Credit;
use App\Models\Document;
use App\Models\Invoice;
@ -37,6 +40,7 @@ use App\Repositories\PaymentRepository;
use App\Repositories\ProductRepository;
use App\Repositories\QuoteRepository;
use App\Repositories\UserRepository;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@ -49,6 +53,7 @@ use Illuminate\Support\Str;
class Import implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use CompanyGatewayFeesAndLimitsSaver;
/**
* @var array
@ -64,7 +69,18 @@ class Import implements ShouldQueue
* @var array
*/
private $available_imports = [
'company', 'users', 'tax_rates', 'clients', 'products', 'invoices', 'quotes', 'payments', 'credits', 'documents',
'company',
'users',
'tax_rates',
'clients',
'products',
'invoices',
'quotes',
'payments',
'credits',
'company_gateways',
'documents',
'client_gateway_tokens',
];
/**
@ -110,6 +126,7 @@ class Import implements ShouldQueue
*/
public function handle()
{
try {
foreach ($this->data as $key => $resource) {
@ -143,7 +160,7 @@ class Import implements ShouldQueue
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
\Log::error($validator->errors());
// \Log::error($validator->errors());
throw new MigrationValidatorFailed($validator->errors());
}
@ -548,11 +565,84 @@ class Import implements ShouldQueue
$this->ids['documents'] = [
"documents_{$old_user_key}" => [
'old' => $old_user_key,
'old' => $resource['id'],
'new' => $document->id,
]
];
}
}
private function processCompanyGateways(array $data) :void
{
CompanyGateway::unguard();
$rules = [
'*.gateway_key' => 'required',
'*.fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(),
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed($validator->errors());
}
foreach ($data as $resource) {
$modified = $resource;
$modified['user_id'] = $this->processUserId($resource);
$modified['company_id'] = $this->company->id;
unset($modified['id']);
if (isset($modified['config'])) {
$modified['config'] = encrypt($modified['config']);
}
if (isset($modified['fees_and_limits'])) {
$modified['fees_and_limits'] = $this->cleanFeesAndLimits($modified['fees_and_limits']);
}
$company_gateway = CompanyGateway::create($modified);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
$this->ids['company_gateways'] = [
"company_gateways_{$old_user_key}" => [
'old' => $resource['id'],
'new' => $company_gateway->id,
]
];
}
}
private function processClientGatewayTokens(array $data) :void
{
ClientGatewayToken::unguard();
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
$modified['company_id'] = $this->company->id;
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
$cgt = ClientGatewayToken::Create($modified);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
$this->ids['client_gateway_tokens'] = [
"client_gateway_tokens_{$old_user_key}" => [
'old' => $resource['id'],
'new' => $cgt->id,
]
];
}
}
/**

View File

@ -0,0 +1,38 @@
<?php
namespace App\Jobs\Util;
use App\Utils\Traits\BulkOptions;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
class UnlinkFile implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $file_path;
protected $disk;
public function __construct(string $disk, string $file_path)
{
$this->file_path = $file_path;
$this->disk = $disk;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Storage::disk($this->disk)->delete($this->file_path);
}
}

View File

@ -215,7 +215,7 @@ class MultiDB
* @param $database
*/
public static function setDB(string $database) : void
{
{
/* This will set the database connection for the request */
config(['database.default' => $database]);
}

View File

@ -2,6 +2,7 @@
namespace App\Mail;
use App\Models\Company;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -14,9 +15,13 @@ class DownloadInvoices extends Mailable
public $file_path;
public function __construct($file_path)
public $company;
public function __construct($file_path, Company $company)
{
$this->file_path = $file_path;
$this->company = $company;
}
/**
@ -27,10 +32,17 @@ class DownloadInvoices extends Mailable
public function build()
{
return $this->from(config('mail.from.address')) //todo this needs to be fixed to handle the hosted version
->subject(ctrans('texts.download_documents',['size'=>'']))
->markdown('email.admin.download_files', [
'file_path' => $this->file_path
]);
return $this->subject(ctrans('texts.download_files'))
->markdown('email.admin.download_files',
[
'url' => $this->file_path,
'logo' => $this->company->present()->logo,
]);
// return $this->from(config('mail.from.address')) //todo this needs to be fixed to handle the hosted version
// ->subject(ctrans('texts.download_documents',['size'=>'']))
// ->markdown('email.admin.download_files', [
// 'file_path' => $this->file_path
// ]);
}
}

View File

@ -32,11 +32,13 @@ use App\Models\TaxRate;
use App\Models\Timezone;
use App\Models\Traits\AccountTrait;
use App\Models\User;
use App\Services\Notification\NotificationService;
use App\Utils\Ninja;
use App\Utils\Traits\CompanySettingsSaver;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\ThrottlesEmail;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Log;
use Laracasts\Presenter\PresentableTrait;
@ -300,7 +302,8 @@ class Company extends BaseModel
public function company_users()
{
return $this->hasMany(CompanyUser::class)->withTimestamps();
//return $this->hasMany(CompanyUser::class)->withTimestamps();
return $this->hasMany(CompanyUser::class);
}
public function owner()
@ -320,4 +323,19 @@ class Company extends BaseModel
{
return 'https://' . $this->subdomain . config('ninja.app_domain');
}
public function notification(Notification $notification)
{
return new NotificationService($this, $notification);
}
public function routeNotificationForSlack($notification)
{
//todo need to return the company channel here for hosted users
//else the env variable for selfhosted
if(config('ninja.environment') == 'selfhosted')
return config('ninja.notification.slack');
else
return $this->settings->system_notifications_slack;
}
}

View File

@ -152,7 +152,10 @@ class User extends Authenticatable implements MustVerifyEmail
*/
public function getCompany()
{
return $this->company;
if($this->company)
return $this->company;
return Company::find( config('ninja.company_id') );
}
/**
@ -298,7 +301,13 @@ class User extends Authenticatable implements MustVerifyEmail
{
//todo need to return the company channel here for hosted users
//else the env variable for selfhosted
return config('ninja.notification.slack');
if(config('ninja.environment') == 'selfhosted')
return config('ninja.notification.slack');
if($this->company())
return $this->company()->settings->system_notifications_slack;
}

View File

@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Mail\Signup\NewSignup;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@ -36,7 +37,8 @@ class NewAccountCreated extends Notification implements ShouldQueue
*/
public function via($notifiable)
{
return ['slack'];
//return ['mail'];
return ['slack','mail'];
}
/**
@ -47,10 +49,25 @@ class NewAccountCreated extends Notification implements ShouldQueue
*/
public function toMail($notifiable)
{
$user_name = $this->user->first_name . " " . $this->user->last_name;
$email = $this->user->email;
$ip = $this->user->ip;
$data = [
'title' => ctrans('texts.new_signup'),
'message' => ctrans('texts.new_signup_text', ['user' => $user_name, 'email' => $email, 'ip' => $ip]),
'url' => config('ninja.web_url'),
'button' => ctrans('texts.account_login'),
'signature' => '',
];
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
->subject(ctrans('texts.new_signup'))
->markdown('email.admin.generic', $data);
}
/**
@ -68,6 +85,9 @@ class NewAccountCreated extends Notification implements ShouldQueue
public function toSlack($notifiable)
{
$this->user->setCompany($this->company);
$user_name = $this->user->first_name . " " . $this->user->last_name;
$email = $this->user->email;
$ip = $this->user->ip;

View File

@ -39,6 +39,7 @@ use App\Listeners\Invoice\InvoiceEmailFailedActivity;
use App\Listeners\Invoice\UpdateInvoiceActivity;
use App\Listeners\Invoice\UpdateInvoiceInvitations;
use App\Listeners\SendVerificationNotification;
use App\Listeners\SetDBListener;
use App\Listeners\User\UpdateUserLastLogin;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Providers;
use App\Libraries\MultiDB;
use Illuminate\Support\ServiceProvider;
class MultiDBProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
}
/**
* Register services.
*
* @return void
*/
public function register()
{
if($this->app->runningInConsole()){
return;
}
$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessing::class, function($event) {
if (isset($event->job->payload()['db'])) {
//\Log::error("Provider Setting DB = ".$event->job->payload()['db']);
MultiDB::setDb($event->job->payload()['db']);
}
}
);
}
}

View File

@ -1,40 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Providers;
use App\Providers\UserSignedUp;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendConfirmationNotification
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param UserSignedUp $event
* @return void
*/
public function handle(UserSignedUp $event)
{
//
}
}

View File

@ -1,45 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Providers;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class UserSignedUp
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@ -23,13 +23,12 @@ class ClientContactRepository extends BaseRepository
{
public function save(array $data, Client $client) : void
{
if(isset($data['contacts']))
$contacts = collect($data['contacts']);
else
$contacts = collect();
collect($client->contacts->pluck('id'))->diff($contacts->pluck('id'))->each(function ($contact) {
$client->contacts->pluck('id')->diff($contacts->pluck('id'))->each(function ($contact) {
ClientContact::destroy($contact);
});
@ -43,7 +42,7 @@ class ClientContactRepository extends BaseRepository
//loop and update/create contacts
$contacts->each(function ($contact) use ($client) {
//$update_contact = null;
$update_contact = null;
if (isset($contact['id'])) {
$update_contact = ClientContact::find($contact['id']);

View File

@ -51,7 +51,8 @@ class CreditRepository extends BaseRepository
$credit->fill($data);
$credit->save();
$credit->number = $credit->client->getNextCreditNumber($credit->client);
if(!$credit->number)
$credit->number = $credit->client->getNextCreditNumber($credit->client);
if (isset($data['invitations'])) {
$invitations = collect($data['invitations']);

View File

@ -66,16 +66,20 @@ class InvoiceRepository extends BaseRepository {
$invitations = collect($data['invitations']);
/* Get array of Keys which have been removed from the invitations array and soft delete each invitation */
collect($invoice->invitations->pluck('key'))->diff($invitations->pluck('key'))->each(function ($invitation) {
InvoiceInvitation::whereRaw("BINARY `key`= ?", [$invitation])->delete();
$invoice->invitations->pluck('key')->diff($invitations->pluck('key'))->each(function ($invitation) {
$invite = $this->getInvitationByKey($invitation);
if($invite)
$invite->forceDelete();
});
foreach ($data['invitations'] as $invitation) {
$inv = false;
if (array_key_exists('key', $invitation)) {
// $inv = InvoiceInvitation::whereKey($invitation['key'])->first();
$inv = InvoiceInvitation::whereRaw("BINARY `key`= ?", [$invitation['key']])->first();
$inv = $this->getInvitationByKey($invitation['key']);
}
if (!$inv) {

View File

@ -102,7 +102,7 @@ class QuoteRepository extends BaseRepository
return $quote->fresh();
}
public function getInvitationByKey($key) :QuoteInvitation
public function getInvitationByKey($key) :?QuoteInvitation
{
return QuoteInvitation::whereRaw("BINARY `key`= ?", [$key])->first();
}

View File

@ -0,0 +1,42 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Services\Notification;
use App\Models\Company;
use App\Services\AbstractService;
use Illuminate\Notifications\Notification as Notifiable;
use Illuminate\Support\Facades\Notification;
class NotificationService extends AbstractService
{
public $company;
public $notification;
public function __construct(Company $company, Notifiable $notification)
{
$this->company = $company;
$this->notification = $notification;
}
public function run()
{
$this->company->owner()->notify($this->notification);
}
}

View File

@ -21,7 +21,7 @@ class InvoiceTransformer extends EntityTransformer
use MakesHash;
protected $defaultIncludes = [
// 'invoice_items',
'invitations'
];
protected $availableIncludes = [
@ -88,6 +88,7 @@ class InvoiceTransformer extends EntityTransformer
'amount' => (float) $invoice->amount,
'balance' => (float) $invoice->balance,
'client_id' => (string) $this->encodePrimaryKey($invoice->client_id),
'vendor_id' => (string) $this->encodePrimaryKey($invoice->vendor_id),
'status_id' => (string) ($invoice->status_id ?: 1),
'design_id' => (string) ($invoice->design_id ?: 1),
'updated_at' => (int)$invoice->updated_at,

View File

@ -179,6 +179,7 @@ return [
App\Providers\RouteServiceProvider::class,
App\Providers\ComposerServiceProvider::class,
Codedge\Updater\UpdaterServiceProvider::class,
App\Providers\MultiDBProvider::class,
],

View File

@ -17,6 +17,7 @@ return [
'date_time_format' => 'Y-m-d H:i',
'daily_email_limit' => 300,
'error_email' => env('ERROR_EMAIL', ''),
'company_id' => 0,
'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller'

View File

@ -9,8 +9,8 @@ $factory->define(App\Models\Client::class, function (Faker $faker) {
'name' => $faker->name(),
'website' => $faker->url,
'private_notes' => $faker->text(200),
'balance' => $faker->numberBetween(0,1000),
'paid_to_date' => $faker->numberBetween(0,10000),
'balance' => 0,
'paid_to_date' => 0,
'vat_number' => $faker->text(25),
'id_number' => $faker->text(20),
'custom_value1' => $faker->text(20),

View File

@ -67,6 +67,7 @@ class CreateUsersTable extends Migration
$table->string('decimal_separator');
$table->string('code');
$table->boolean('swap_currency_symbol')->default(false);
$table->decimal('exchange_rate', 6, 6);
});
@ -261,6 +262,7 @@ class CreateUsersTable extends Migration
$table->unsignedInteger('avatar_width')->nullable();
$table->unsignedInteger('avatar_height')->nullable();
$table->unsignedInteger('avatar_size')->nullable();
$table->datetime('last_login')->nullable();
$table->mediumText('signature')->nullable();
$table->string('password');

View File

@ -3113,6 +3113,19 @@ $LANG = array(
'next_send_date' => 'Next send date',
'cycles_remaining' => 'Cycles remaining',
'i_understand_delete' => 'I understand, delete',
'download_files' => 'Download Files',
'download_timeframe' => 'Use this link to download your files, the link will expire in 1 hour.',
'new_signup' => 'New Signup',
'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip'
);
return $LANG;

View File

@ -1,20 +1,25 @@
@component('mail::layout')
@component('email.components.layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
Download
@endcomponent
@component('email.components.header', ['p' => ''])
<img src="{{ $logo }}" alt="Company Logo" style="display: block">
@endcomponent
@endslot
{{-- Body --}}
{{ $file_path }}
@lang('texts.download_timeframe')
@component('email.components.button', ['url' => $url])
@lang('texts.download')
@endcomponent
@slot('signature')
InvoiceNinja
@endslot
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('ninja.app_name') }}.
@endcomponent
@component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '&copy; InvoiceNinja'])
For any info, please visit InvoiceNinja.
@endcomponent
@endslot
@endcomponent
@endcomponent

View File

@ -0,0 +1,25 @@
@component('email.components.layout')
@slot('header')
@component('email.components.header', ['p' => ''])
@lang($title)
@endcomponent
@endslot
@lang($message)
@component('email.components.button', ['url' => $url])
@lang($button)
@endcomponent
@slot('signature')
{{ $signature }}
@endslot
@slot('footer')
@component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '&copy; InvoiceNinja'])
For any info, please visit InvoiceNinja.
@endcomponent
@endslot
@endcomponent

View File

@ -1,4 +1,8 @@
@if()
@component('email.components.layout-dark')
@else
@component('email.components.layout')
@endif
@slot('header')
@component('email.components.header', ['p' => 'Your upgrade has completed!'])

View File

@ -0,0 +1,150 @@
<?php
namespace Tests\Integration;
use App\DataMapper\CompanySettings;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Events\Payment\PaymentWasCreated;
use App\Factory\CompanyUserFactory;
use App\Jobs\Invoice\MarkInvoicePaid;
use App\Models\Account;
use App\Models\Activity;
use App\Models\Company;
use App\Models\CompanyLedger;
use App\Models\CompanyToken;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\User;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Tests\MockAccountData;
use Tests\TestCase;
/** @test*/
class CompanyLedgerTest extends TestCase
{
use DatabaseTransactions;
use MakesHash;
public $company;
public $client;
public $user;
public $token;
public $account;
public function setUp() :void
{
parent::setUp();
/* Warm up the cache !*/
$cached_tables = config('ninja.cached_tables');
foreach ($cached_tables as $name => $class) {
// check that the table exists in case the migration is pending
if (! Schema::hasTable((new $class())->getTable())) {
continue;
}
if ($name == 'payment_terms') {
$orderBy = 'num_days';
} elseif ($name == 'fonts') {
$orderBy = 'sort_order';
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
$orderBy = 'name';
} else {
$orderBy = 'id';
}
$tableData = $class::orderBy($orderBy)->get();
if ($tableData->count()) {
Cache::forever($name, $tableData);
}
}
$this->account = factory(\App\Models\Account::class)->create();
$this->company = factory(\App\Models\Company::class)->create([
'account_id' => $this->account->id,
]);
$settings = CompanySettings::defaults();
$settings->company_logo = 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png';
$settings->website = 'www.invoiceninja.com';
$settings->address1 = 'Address 1';
$settings->address2 = 'Address 2';
$settings->city = 'City';
$settings->state = 'State';
$settings->postal_code = 'Postal Code';
$settings->phone = '555-343-2323';
$settings->email = 'user@example.com';
$settings->country_id = '840';
$settings->vat_number = 'vat number';
$settings->id_number = 'id number';
$this->company->settings = $settings;
$this->company->save();
$this->account->default_company_id = $this->company->id;
$this->account->save();
$this->user = User::whereEmail('user@example.com')->first();
if(!$this->user){
$this->user = factory(\App\Models\User::class)->create([
'password' => Hash::make('ALongAndBriliantPassword'),
'confirmation_code' => $this->createDbHash(config('database.default'))
]);
}
$cu = CompanyUserFactory::create($this->user->id, $this->company->id, $this->account->id);
$cu->is_owner = true;
$cu->is_admin = true;
$cu->save();
$this->token = \Illuminate\Support\Str::random(64);
$company_token = CompanyToken::create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'account_id' => $this->account->id,
'name' => 'test token',
'token' => $this->token,
]);
$this->client = factory(\App\Models\Client::class)->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
]);
factory(\App\Models\ClientContact::class,1)->create([
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'is_primary' => 1,
'send_email' => true,
]);
}
public function testBaseLine()
{
$this->assertEquals($this->company->invoices->count(), 0);
$this->assertEquals($this->company->clients->count(), 1);
$this->assertEquals($this->client->balance, 0);
}
}

View File

@ -10,7 +10,9 @@ use App\Jobs\Util\StartMigration;
use App\Mail\MigrationFailed;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\Credit;
use App\Models\Document;
use App\Models\Invoice;
@ -329,10 +331,9 @@ class ImportTest extends TestCase
$this->invoice->forceDelete();
$this->quote->forceDelete();
// $migration_file = base_path() . '/tests/Unit/Migration/migration.json';
// $this->migration_array = json_decode(file_get_contents($migration_file), 1);
// $migration_file = base_path() . '/tests/Unit/Migration/migration.json';
// $this->migration_array = json_decode(file_get_contents($migration_file), 1);
Import::dispatchNow($this->migration_array, $this->company, $this->user);
@ -407,7 +408,7 @@ class ImportTest extends TestCase
}
}
/*foreach ($this->migration_array['credits'] as $key => $credit) {
foreach ($this->migration_array['credits'] as $key => $credit) {
// The Import::processCredits() does insert the credit record with number: 0053,
// .. however this part of the code doesn't see it at all.
@ -418,7 +419,36 @@ class ImportTest extends TestCase
if (!$record) {
$differences['credits']['missing'][] = $credit['id'];
}
}*/
}
foreach ($this->migration_array['company_gateways'] as $key => $company_gateway) {
// The Import::processCredits() does insert the credit record with number: 0053,
// .. however this part of the code doesn't see it at all.
$record = CompanyGateway::where('gateway_key' ,$company_gateway['gateway_key'])
->first();
if (!$record) {
$differences['company_gateways']['missing'][] = $company_gateway['id'];
}
}
foreach ($this->migration_array['client_gateway_tokens'] as $key => $cgt) {
// The Import::processCredits() does insert the credit record with number: 0053,
// .. however this part of the code doesn't see it at all.
$record = ClientGatewayToken::where('token' ,$cgt['token'])
->first();
if (!$record) {
$differences['client_gateway_tokens']['missing'][] = $cgt['id'];
}
}
//@TODO we can uncomment tests for documents when we have imported expenses.
// foreach ($this->migration_array['documents'] as $key => $document) {
@ -433,7 +463,8 @@ class ImportTest extends TestCase
// }
// }
\Log::error($differences);
//\Log::error($differences);
$this->assertCount(0, $differences);
}
@ -453,6 +484,18 @@ class ImportTest extends TestCase
$this->assertGreaterThan($original, ClientContact::count());
}
public function testClientGatewayTokensImport()
{
$this->invoice->forceDelete();
$this->quote->forceDelete();
$original = ClientGatewayToken::count();
Import::dispatchNow($this->migration_array, $this->company, $this->user);
$this->assertGreaterThan($original, ClientGatewayToken::count());
}
public function testDocumentsImport()
{
$this->invoice->forceDelete();
@ -462,11 +505,11 @@ class ImportTest extends TestCase
Import::dispatchNow($this->migration_array, $this->company, $this->user);
$this->assertGreaterThan($original, Document::count());
// $this->assertGreaterThan($original, Document::count());
$document = Document::first();
$this->assertNotNull(Invoice::find($document->documentable_id)->documents);
$this->assertNotNull($document->documentable);
// $this->assertNotNull(Invoice::find($document->documentable_id)->documents);
// $this->assertNotNull($document->documentable);
}
}

View File

@ -19,7 +19,7 @@
"invoice_text1": "Service Date"
},
"created_at": "2020-02-11",
"updated_at": "2020-02-21",
"updated_at": "2020-02-22",
"settings": {
"timezone_id": "15",
"date_format_id": "1",
@ -114,7 +114,7 @@
"google_2fa_secret": null,
"accepted_terms_version": "1.0.1",
"password": "$2y$10$pDVj9LrItbYsvEenqOQe7.fSgdiIYzoLF86YnVtVVMLJzaBDI4iHC",
"remember_token": "dhZnqoBgFsxcBn2tesbAfgQ9US4TkxCsHaF8HjqN0IQuoAZ7Ptfiw3jNZY4U",
"remember_token": "nMizwyeTun32YxDB1NPpdiWzb0kMeAgDBlvJCFAgUwOA8yo8qwiEGpG1xwUS",
"created_at": "2020-02-11",
"updated_at": "2020-02-11",
"deleted_at": null
@ -17126,134 +17126,6 @@
"deleted_at": null
}
],
"documents": [
{
"id": 1,
"user_id": 1,
"company_id": 1,
"invoice_id": 28,
"expense_id": null,
"path": "zuom5k2exmedyhztpgnkmwhwkzzxpbtk\/f8e8b45317131b60cd3177c13a5761ab278da43d.pdf",
"preview": "",
"name": "0019.pdf",
"type": "pdf",
"disk": "documents",
"hash": "f8e8b45317131b60cd3177c13a5761ab278da43d",
"size": 51397,
"width": null,
"height": null,
"created_at": "2020-02-18",
"updated_at": "2020-02-18"
},
{
"id": 2,
"user_id": 1,
"company_id": 1,
"invoice_id": null,
"expense_id": null,
"path": "zuom5k2exmedyhztpgnkmwhwkzzxpbtk\/ddbba2265fe3e3d5ed1ce72bcc3e67e6dc6d9a2e.pdf",
"preview": "",
"name": "0001.pdf",
"type": "pdf",
"disk": "documents",
"hash": "ddbba2265fe3e3d5ed1ce72bcc3e67e6dc6d9a2e",
"size": 49393,
"width": null,
"height": null,
"created_at": "2020-02-18",
"updated_at": "2020-02-18"
},
{
"id": 3,
"user_id": 1,
"company_id": 1,
"invoice_id": null,
"expense_id": 60,
"path": "zuom5k2exmedyhztpgnkmwhwkzzxpbtk\/858c794d4988ebcce27e01adb1cd8b2ba98d5668.pdf",
"preview": "",
"name": "0001.pdf",
"type": "pdf",
"disk": "documents",
"hash": "858c794d4988ebcce27e01adb1cd8b2ba98d5668",
"size": 51250,
"width": null,
"height": null,
"created_at": "2020-02-18",
"updated_at": "2020-02-18"
},
{
"id": 4,
"user_id": 1,
"company_id": 1,
"invoice_id": 28,
"expense_id": null,
"path": "zuom5k2exmedyhztpgnkmwhwkzzxpbtk\/ed80d3d94084cefa54d41d3989fb99a757747276.pdf",
"preview": "",
"name": "Invoice_0028 (11).pdf",
"type": "pdf",
"disk": "documents",
"hash": "ed80d3d94084cefa54d41d3989fb99a757747276",
"size": 74468,
"width": null,
"height": null,
"created_at": "2020-02-21",
"updated_at": "2020-02-21"
},
{
"id": 5,
"user_id": 1,
"company_id": 1,
"invoice_id": null,
"expense_id": 51,
"path": "zuom5k2exmedyhztpgnkmwhwkzzxpbtk\/c81cf7f4bae0c1e32b87835a39d056110a0d63ec.pdf",
"preview": "",
"name": "Invoice_0028 (8).pdf",
"type": "pdf",
"disk": "documents",
"hash": "c81cf7f4bae0c1e32b87835a39d056110a0d63ec",
"size": 28390,
"width": null,
"height": null,
"created_at": "2020-02-21",
"updated_at": "2020-02-21"
},
{
"id": 6,
"user_id": 1,
"company_id": 1,
"invoice_id": null,
"expense_id": 51,
"path": "zuom5k2exmedyhztpgnkmwhwkzzxpbtk\/7ce4feb538a975856e29c47709698b5b792ec3b4.pdf",
"preview": "",
"name": "Invoice_0028 (6).pdf",
"type": "pdf",
"disk": "documents",
"hash": "7ce4feb538a975856e29c47709698b5b792ec3b4",
"size": 29613,
"width": null,
"height": null,
"created_at": "2020-02-21",
"updated_at": "2020-02-21"
},
{
"id": 7,
"user_id": 1,
"company_id": 1,
"invoice_id": null,
"expense_id": 51,
"path": "zuom5k2exmedyhztpgnkmwhwkzzxpbtk\/1bbf0c9bd9024ceb83064daeeab9386d68097410.pdf",
"preview": "",
"name": "Invoice_0028 (5).pdf",
"type": "pdf",
"disk": "documents",
"hash": "1bbf0c9bd9024ceb83064daeeab9386d68097410",
"size": 28268,
"width": null,
"height": null,
"created_at": "2020-02-21",
"updated_at": "2020-02-21"
}
],
"company_gateways": [
{
"id": 3,
@ -17264,19 +17136,33 @@
"show_billing_address": null,
"show_shipping_address": 1,
"update_details": null,
"config": "{\"apiKey\":\"sk_test_faU9gVB7Hx19fCTo0e5ggZ0x\",\"publishableKey\":\"pk_test_iRPDj3jLiQs0Guae0lvSHaOD\",\"plaidClientId\":\"\",\"plaidSecret\":\"\",\"plaidPublicKey\":\"\",\"enableAlipay\":true,\"enableSofort\":true,\"enableSepa\":false,\"enableBitcoin\":false,\"enableApplePay\":true,\"enableAch\":true}",
"fees_and_limits": {
"min_limit": 234,
"max_limit": 65317,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
"config": {
"apiKey": "sk_test_faU9gVB7Hx19fCTo0e5ggZ0x",
"publishableKey": "pk_test_iRPDj3jLiQs0Guae0lvSHaOD",
"plaidClientId": "",
"plaidSecret": "",
"plaidPublicKey": "",
"enableAlipay": true,
"enableSofort": true,
"enableSepa": false,
"enableBitcoin": false,
"enableApplePay": true,
"enableAch": true
},
"fees_and_limits": [
{
"min_limit": 234,
"max_limit": 65317,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
}
],
"custom_value1": "",
"custom_value2": "",
"custom_value3": "",
@ -17291,7 +17177,19 @@
"show_billing_address": null,
"show_shipping_address": 1,
"update_details": null,
"config": "{\"apiKey\":\"sk_test_faU9gVB7Hx19fCTo0e5ggZ0x\",\"publishableKey\":\"pk_test_iRPDj3jLiQs0Guae0lvSHaOD\",\"plaidClientId\":\"\",\"plaidSecret\":\"\",\"plaidPublicKey\":\"\",\"enableAlipay\":true,\"enableSofort\":true,\"enableSepa\":false,\"enableBitcoin\":false,\"enableApplePay\":true,\"enableAch\":true}",
"config": {
"apiKey": "sk_test_faU9gVB7Hx19fCTo0e5ggZ0x",
"publishableKey": "pk_test_iRPDj3jLiQs0Guae0lvSHaOD",
"plaidClientId": "",
"plaidSecret": "",
"plaidPublicKey": "",
"enableAlipay": true,
"enableSofort": true,
"enableSepa": false,
"enableBitcoin": false,
"enableApplePay": true,
"enableAch": true
},
"fees_and_limits": {},
"custom_value1": "",
"custom_value2": "",
@ -17307,19 +17205,33 @@
"show_billing_address": null,
"show_shipping_address": 1,
"update_details": null,
"config": "{\"apiKey\":\"sk_test_faU9gVB7Hx19fCTo0e5ggZ0x\",\"publishableKey\":\"pk_test_iRPDj3jLiQs0Guae0lvSHaOD\",\"plaidClientId\":\"\",\"plaidSecret\":\"\",\"plaidPublicKey\":\"\",\"enableAlipay\":true,\"enableSofort\":true,\"enableSepa\":false,\"enableBitcoin\":false,\"enableApplePay\":true,\"enableAch\":true}",
"fees_and_limits": {
"min_limit": 147,
"max_limit": 53254,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
"config": {
"apiKey": "sk_test_faU9gVB7Hx19fCTo0e5ggZ0x",
"publishableKey": "pk_test_iRPDj3jLiQs0Guae0lvSHaOD",
"plaidClientId": "",
"plaidSecret": "",
"plaidPublicKey": "",
"enableAlipay": true,
"enableSofort": true,
"enableSepa": false,
"enableBitcoin": false,
"enableApplePay": true,
"enableAch": true
},
"fees_and_limits": [
{
"min_limit": 147,
"max_limit": 53254,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
}
],
"custom_value1": "",
"custom_value2": "",
"custom_value3": "",
@ -17334,19 +17246,33 @@
"show_billing_address": null,
"show_shipping_address": 1,
"update_details": null,
"config": "{\"apiKey\":\"sk_test_faU9gVB7Hx19fCTo0e5ggZ0x\",\"publishableKey\":\"pk_test_iRPDj3jLiQs0Guae0lvSHaOD\",\"plaidClientId\":\"\",\"plaidSecret\":\"\",\"plaidPublicKey\":\"\",\"enableAlipay\":true,\"enableSofort\":true,\"enableSepa\":false,\"enableBitcoin\":false,\"enableApplePay\":true,\"enableAch\":true}",
"fees_and_limits": {
"min_limit": 155,
"max_limit": 72857,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
"config": {
"apiKey": "sk_test_faU9gVB7Hx19fCTo0e5ggZ0x",
"publishableKey": "pk_test_iRPDj3jLiQs0Guae0lvSHaOD",
"plaidClientId": "",
"plaidSecret": "",
"plaidPublicKey": "",
"enableAlipay": true,
"enableSofort": true,
"enableSepa": false,
"enableBitcoin": false,
"enableApplePay": true,
"enableAch": true
},
"fees_and_limits": [
{
"min_limit": 155,
"max_limit": 72857,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
}
],
"custom_value1": "",
"custom_value2": "",
"custom_value3": "",
@ -17361,19 +17287,33 @@
"show_billing_address": null,
"show_shipping_address": 1,
"update_details": null,
"config": "{\"apiKey\":\"sk_test_faU9gVB7Hx19fCTo0e5ggZ0x\",\"publishableKey\":\"pk_test_iRPDj3jLiQs0Guae0lvSHaOD\",\"plaidClientId\":\"\",\"plaidSecret\":\"\",\"plaidPublicKey\":\"\",\"enableAlipay\":true,\"enableSofort\":true,\"enableSepa\":false,\"enableBitcoin\":false,\"enableApplePay\":true,\"enableAch\":true}",
"fees_and_limits": {
"min_limit": 139,
"max_limit": 71349,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
"config": {
"apiKey": "sk_test_faU9gVB7Hx19fCTo0e5ggZ0x",
"publishableKey": "pk_test_iRPDj3jLiQs0Guae0lvSHaOD",
"plaidClientId": "",
"plaidSecret": "",
"plaidPublicKey": "",
"enableAlipay": true,
"enableSofort": true,
"enableSepa": false,
"enableBitcoin": false,
"enableApplePay": true,
"enableAch": true
},
"fees_and_limits": [
{
"min_limit": 139,
"max_limit": 71349,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
}
],
"custom_value1": "",
"custom_value2": "",
"custom_value3": "",
@ -17388,19 +17328,33 @@
"show_billing_address": null,
"show_shipping_address": 1,
"update_details": null,
"config": "{\"apiKey\":\"sk_test_faU9gVB7Hx19fCTo0e5ggZ0x\",\"publishableKey\":\"pk_test_iRPDj3jLiQs0Guae0lvSHaOD\",\"plaidClientId\":\"\",\"plaidSecret\":\"\",\"plaidPublicKey\":\"\",\"enableAlipay\":true,\"enableSofort\":true,\"enableSepa\":false,\"enableBitcoin\":false,\"enableApplePay\":true,\"enableAch\":true}",
"fees_and_limits": {
"min_limit": 151,
"max_limit": 74365,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
"config": {
"apiKey": "sk_test_faU9gVB7Hx19fCTo0e5ggZ0x",
"publishableKey": "pk_test_iRPDj3jLiQs0Guae0lvSHaOD",
"plaidClientId": "",
"plaidSecret": "",
"plaidPublicKey": "",
"enableAlipay": true,
"enableSofort": true,
"enableSepa": false,
"enableBitcoin": false,
"enableApplePay": true,
"enableAch": true
},
"fees_and_limits": [
{
"min_limit": 151,
"max_limit": 74365,
"fee_amount": "0.00",
"fee_percent": "0.000",
"tax_name1": null,
"tax_rate1": null,
"tax_name2": null,
"tax_rate2": null,
"tax_name3": "",
"tax_rate3": 0
}
],
"custom_value1": "",
"custom_value2": "",
"custom_value3": "",