1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Merge branch 'v5-develop' into feature-einvoice-quote

Signed-off-by: Lars Kusch <lars@lars-kusch.de>
This commit is contained in:
Lars Kusch 2024-03-19 15:45:40 +01:00 committed by GitHub
commit ee147e0e4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
182 changed files with 112687 additions and 10432 deletions

View File

@ -1 +1 @@
5.8.35
5.8.37

View File

@ -13,5 +13,10 @@ namespace Illuminate\Contracts\Mail
{
return true;
}
public function brevo_config(string $key)
{
return true;
}
}
}

View File

@ -891,7 +891,7 @@ class CheckData extends Command
$this->logMessage("Fixing country for # {$client->id}");
});
Client::query()->whereNull("settings->currency_id")->cursor()->each(function ($client) {
Client::query()->whereNull("settings->currency_id")->orWhereJsonContains('settings', ['currency_id' => ''])->cursor()->each(function ($client) {
$settings = $client->settings;
$settings->currency_id = (string)$client->company->settings->currency_id;
$client->settings = $settings;
@ -933,7 +933,6 @@ class CheckData extends Command
});
Invoice::withTrashed()
->where("partial", 0)
->whereNotNull("partial_due_date")
@ -967,7 +966,6 @@ class CheckData extends Command
});
CompanyUser::whereDoesntHave('user')
->cursor()
->when(Ninja::isHosted())
@ -977,6 +975,14 @@ class CheckData extends Command
});
$cus = CompanyUser::withTrashed()
->whereHas("user", function ($query) {
$query->whereColumn("users.account_id", "!=", "company_user.account_id");
})->pluck('id')->implode(",");
$this->logMessage("Cross Linked CompanyUser ids # {$cus}");
}
}

View File

@ -11,53 +11,54 @@
namespace App\Console\Commands;
use App\DataMapper\ClientRegistrationFields;
use App\DataMapper\CompanySettings;
use App\DataMapper\FeesAndLimits;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
use App\Factory\GroupSettingFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Factory\RecurringInvoiceFactory;
use App\Factory\SubscriptionFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Company\CreateCompanyTaskStatuses;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\BankIntegration;
use App\Models\BankTransaction;
use App\Models\BankTransactionRule;
use stdClass;
use Carbon\Carbon;
use Faker\Factory;
use App\Models\Task;
use App\Models\User;
use App\Utils\Ninja;
use App\Models\Quote;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyToken;
use App\Models\Country;
use App\Models\Credit;
use App\Models\Vendor;
use App\Models\Account;
use App\Models\Company;
use App\Models\Country;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Libraries\MultiDB;
use App\Models\TaskStatus;
use App\Models\CompanyToken;
use App\Models\ClientContact;
use App\Models\VendorContact;
use App\Repositories\InvoiceRepository;
use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter;
use App\Models\CompanyGateway;
use App\Factory\InvoiceFactory;
use App\Models\BankIntegration;
use App\Models\BankTransaction;
use App\Utils\Traits\MakesHash;
use Carbon\Carbon;
use Faker\Factory;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use App\Models\RecurringInvoice;
use App\DataMapper\FeesAndLimits;
use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Models\BankTransactionRule;
use App\Factory\GroupSettingFactory;
use App\Factory\SubscriptionFactory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Cache;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Support\Facades\Schema;
use stdClass;
use App\Repositories\InvoiceRepository;
use App\Factory\RecurringInvoiceFactory;
use App\Events\Invoice\InvoiceWasCreated;
use App\DataMapper\ClientRegistrationFields;
use App\Jobs\Company\CreateCompanyTaskStatuses;
use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
class CreateSingleAccount extends Command
{
@ -951,7 +952,7 @@ class CreateSingleAccount extends Command
}
if (config('ninja.testvars.paytrace.decrypted') && ($this->gateway == 'all' || $this->gateway == 'paytrace')) {
if (config('ninja.testvars.paytrace') && ($this->gateway == 'all' || $this->gateway == 'paytrace')) {
$cg = new CompanyGateway();
$cg->company_id = $company->id;
$cg->user_id = $user->id;
@ -960,7 +961,7 @@ class CreateSingleAccount extends Command
$cg->require_billing_address = true;
$cg->require_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.paytrace.decrypted'));
$cg->config = encrypt(config('ninja.testvars.paytrace'));
$cg->save();
@ -1015,6 +1016,85 @@ class CreateSingleAccount extends Command
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
if (config('ninja.testvars.eway') && ($this->gateway == 'all' || $this->gateway == 'eway')) {
$cg = new CompanyGateway();
$cg->company_id = $company->id;
$cg->user_id = $user->id;
$cg->gateway_key = '944c20175bbe6b9972c05bcfe294c2c7';
$cg->require_cvv = true;
$cg->require_billing_address = true;
$cg->require_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.eway'));
$cg->save();
$gateway_types = $cg->driver()->gatewayTypes();
$fees_and_limits = new stdClass();
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits();
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
if (config('ninja.testvars.gocardless') && ($this->gateway == 'all' || $this->gateway == 'gocardless')) {
$c_settings = ClientSettings::defaults();
$c_settings->currency_id = '2';
$client = Client::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'name' => 'cypress',
'country_id' => 826,
'settings' => $c_settings
]);
$cg = new CompanyGateway();
$cg->company_id = $company->id;
$cg->user_id = $user->id;
$cg->gateway_key = 'b9886f9257f0c6ee7c302f1c74475f6c';
$cg->require_cvv = true;
$cg->require_billing_address = true;
$cg->require_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.gocardless'));
$cg->save();
$gateway_types = $cg->driver($client)->gatewayTypes();
$fees_and_limits = new stdClass();
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits();
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
if (config('ninja.testvars.forte') && ($this->gateway == 'all' || $this->gateway == 'forte')) {
$cg = new CompanyGateway();
$cg->company_id = $company->id;
$cg->user_id = $user->id;
$cg->gateway_key = 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs';
$cg->require_cvv = true;
$cg->require_billing_address = true;
$cg->require_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.forte'));
$cg->save();
$gateway_types = $cg->driver()->gatewayTypes();
$fees_and_limits = new stdClass();
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits();
$cg->fees_and_limits = $fees_and_limits;
$cg->save();
}
}
private function createRecurringInvoice(Client $client)

View File

@ -229,7 +229,7 @@ class CompanySettings extends BaseSettings
public $require_quote_signature = false; //@TODO ben to confirm
//email settings
public $email_sending_method = 'default'; //enum 'default','gmail','office365' 'client_postmark', 'client_mailgun', 'mailgun' //@implemented
public $email_sending_method = 'default'; //enum 'default','gmail','office365' 'client_postmark', 'client_mailgun', 'mailgun', 'client_brevo' //@implemented
public $gmail_sending_user_id = '0'; //@implemented
@ -451,6 +451,8 @@ class CompanySettings extends BaseSettings
public $mailgun_endpoint = 'api.mailgun.net'; //api.eu.mailgun.net
public $brevo_secret = '';
public $auto_bill_standard_invoices = false;
public $email_alignment = 'center'; // center , left, right
@ -497,7 +499,10 @@ class CompanySettings extends BaseSettings
public $use_unapplied_payment = 'off'; //always, option, off //@implemented
public $enable_rappen_rounding = false;
public static $casts = [
'enable_rappen_rounding' => 'bool',
'use_unapplied_payment' => 'string',
'show_pdfhtml_on_mobile' => 'bool',
'payment_email_all_contacts' => 'bool',
@ -517,262 +522,263 @@ class CompanySettings extends BaseSettings
'show_task_item_description' => 'bool',
'allow_billable_task_items' => 'bool',
'accept_client_input_quote_approval' => 'bool',
'custom_sending_email' => 'string',
'show_paid_stamp' => 'bool',
'show_shipping_address' => 'bool',
'company_logo_size' => 'string',
'show_email_footer' => 'bool',
'email_alignment' => 'string',
'auto_bill_standard_invoices' => 'bool',
'postmark_secret' => 'string',
'mailgun_secret' => 'string',
'mailgun_domain' => 'string',
'send_email_on_mark_paid' => 'bool',
'vendor_portal_enable_uploads' => 'bool',
'besr_id' => 'string',
'qr_iban' => 'string',
'email_subject_purchase_order' => 'string',
'email_template_purchase_order' => 'string',
'require_purchase_order_signature' => 'bool',
'purchase_order_public_notes' => 'string',
'purchase_order_terms' => 'string',
'purchase_order_design_id' => 'string',
'purchase_order_footer' => 'string',
'purchase_order_number_pattern' => 'string',
'page_numbering_alignment' => 'string',
'page_numbering' => 'bool',
'auto_archive_invoice_cancelled' => 'bool',
'email_from_name' => 'string',
'show_all_tasks_client_portal' => 'string',
'entity_send_time' => 'int',
'shared_invoice_credit_counter' => 'bool',
'reply_to_name' => 'string',
'hide_empty_columns_on_pdf' => 'bool',
'enable_reminder_endless' => 'bool',
'use_credits_payment' => 'string',
'recurring_invoice_number_pattern' => 'string',
'recurring_invoice_number_counter' => 'int',
'custom_sending_email' => 'string',
'show_paid_stamp' => 'bool',
'show_shipping_address' => 'bool',
'company_logo_size' => 'string',
'show_email_footer' => 'bool',
'email_alignment' => 'string',
'auto_bill_standard_invoices' => 'bool',
'postmark_secret' => 'string',
'mailgun_secret' => 'string',
'mailgun_domain' => 'string',
'brevo_secret' => 'string',
'send_email_on_mark_paid' => 'bool',
'vendor_portal_enable_uploads' => 'bool',
'besr_id' => 'string',
'qr_iban' => 'string',
'email_subject_purchase_order' => 'string',
'email_template_purchase_order' => 'string',
'require_purchase_order_signature' => 'bool',
'purchase_order_public_notes' => 'string',
'purchase_order_terms' => 'string',
'purchase_order_design_id' => 'string',
'purchase_order_footer' => 'string',
'purchase_order_number_pattern' => 'string',
'page_numbering_alignment' => 'string',
'page_numbering' => 'bool',
'auto_archive_invoice_cancelled' => 'bool',
'email_from_name' => 'string',
'show_all_tasks_client_portal' => 'string',
'entity_send_time' => 'int',
'shared_invoice_credit_counter' => 'bool',
'reply_to_name' => 'string',
'hide_empty_columns_on_pdf' => 'bool',
'enable_reminder_endless' => 'bool',
'use_credits_payment' => 'string',
'recurring_invoice_number_pattern' => 'string',
'recurring_invoice_number_counter' => 'int',
'client_portal_under_payment_minimum' => 'float',
'auto_bill_date' => 'string',
'primary_color' => 'string',
'secondary_color' => 'string',
'client_portal_allow_under_payment' => 'bool',
'client_portal_allow_over_payment' => 'bool',
'auto_bill' => 'string',
'lock_invoices' => 'string',
'client_portal_terms' => 'string',
'client_portal_privacy_policy' => 'string',
'client_can_register' => 'bool',
'portal_design_id' => 'string',
'late_fee_endless_percent' => 'float',
'late_fee_endless_amount' => 'float',
'auto_email_invoice' => 'bool',
'reminder_send_time' => 'int',
'email_sending_method' => 'string',
'gmail_sending_user_id' => 'string',
'counter_number_applied' => 'string',
'quote_number_applied' => 'string',
'email_subject_custom1' => 'string',
'email_subject_custom2' => 'string',
'email_subject_custom3' => 'string',
'email_template_custom1' => 'string',
'email_template_custom2' => 'string',
'email_template_custom3' => 'string',
'enable_reminder1' => 'bool',
'enable_reminder2' => 'bool',
'enable_reminder3' => 'bool',
'num_days_reminder1' => 'int',
'num_days_reminder2' => 'int',
'num_days_reminder3' => 'int',
'schedule_reminder1' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date)
'schedule_reminder2' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date)
'schedule_reminder3' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date)
'late_fee_amount1' => 'float',
'late_fee_amount2' => 'float',
'late_fee_amount3' => 'float',
'late_fee_percent1' => 'float',
'late_fee_percent2' => 'float',
'late_fee_percent3' => 'float',
'endless_reminder_frequency_id' => 'integer',
'auto_bill_date' => 'string',
'primary_color' => 'string',
'secondary_color' => 'string',
'client_portal_allow_under_payment' => 'bool',
'client_portal_allow_over_payment' => 'bool',
'auto_bill' => 'string',
'lock_invoices' => 'string',
'client_portal_terms' => 'string',
'client_portal_privacy_policy' => 'string',
'client_can_register' => 'bool',
'portal_design_id' => 'string',
'late_fee_endless_percent' => 'float',
'late_fee_endless_amount' => 'float',
'auto_email_invoice' => 'bool',
'reminder_send_time' => 'int',
'email_sending_method' => 'string',
'gmail_sending_user_id' => 'string',
'counter_number_applied' => 'string',
'quote_number_applied' => 'string',
'email_subject_custom1' => 'string',
'email_subject_custom2' => 'string',
'email_subject_custom3' => 'string',
'email_template_custom1' => 'string',
'email_template_custom2' => 'string',
'email_template_custom3' => 'string',
'enable_reminder1' => 'bool',
'enable_reminder2' => 'bool',
'enable_reminder3' => 'bool',
'num_days_reminder1' => 'int',
'num_days_reminder2' => 'int',
'num_days_reminder3' => 'int',
'schedule_reminder1' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date)
'schedule_reminder2' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date)
'schedule_reminder3' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date)
'late_fee_amount1' => 'float',
'late_fee_amount2' => 'float',
'late_fee_amount3' => 'float',
'late_fee_percent1' => 'float',
'late_fee_percent2' => 'float',
'late_fee_percent3' => 'float',
'endless_reminder_frequency_id' => 'integer',
'client_online_payment_notification' => 'bool',
'client_manual_payment_notification' => 'bool',
'document_email_attachment' => 'bool',
'enable_client_portal_password' => 'bool',
'enable_email_markup' => 'bool',
'enable_client_portal_dashboard' => 'bool',
'enable_client_portal' => 'bool',
'email_template_statement' => 'string',
'email_subject_statement' => 'string',
'signature_on_pdf' => 'bool',
'quote_footer' => 'string',
'page_size' => 'string',
'page_layout' => 'string',
'font_size' => 'int',
'primary_font' => 'string',
'secondary_font' => 'string',
'hide_paid_to_date' => 'bool',
'embed_documents' => 'bool',
'all_pages_header' => 'bool',
'all_pages_footer' => 'bool',
'project_number_pattern' => 'string',
'project_number_counter' => 'int',
'task_number_pattern' => 'string',
'task_number_counter' => 'int',
'expense_number_pattern' => 'string',
'expense_number_counter' => 'int',
'recurring_expense_number_pattern' => 'string',
'recurring_expense_number_counter' => 'int',
'recurring_quote_number_pattern' => 'string',
'recurring_quote_number_counter' => 'int',
'vendor_number_pattern' => 'string',
'vendor_number_counter' => 'int',
'ticket_number_pattern' => 'string',
'ticket_number_counter' => 'int',
'payment_number_pattern' => 'string',
'payment_number_counter' => 'int',
'reply_to_email' => 'string',
'bcc_email' => 'string',
'pdf_email_attachment' => 'bool',
'ubl_email_attachment' => 'bool',
'email_style' => 'string',
'email_style_custom' => 'string',
'company_gateway_ids' => 'string',
'address1' => 'string',
'address2' => 'string',
'city' => 'string',
'company_logo' => 'string',
'country_id' => 'string',
'client_number_pattern' => 'string',
'client_number_counter' => 'integer',
'credit_number_pattern' => 'string',
'credit_number_counter' => 'integer',
'currency_id' => 'string',
'custom_value1' => 'string',
'custom_value2' => 'string',
'custom_value3' => 'string',
'custom_value4' => 'string',
'custom_message_dashboard' => 'string',
'custom_message_unpaid_invoice' => 'string',
'custom_message_paid_invoice' => 'string',
'custom_message_unapproved_quote' => 'string',
'default_task_rate' => 'float',
'email_signature' => 'string',
'email_subject_invoice' => 'string',
'email_subject_quote' => 'string',
'email_subject_credit' => 'string',
'email_subject_payment' => 'string',
'email_subject_payment_partial' => 'string',
'email_template_invoice' => 'string',
'email_template_quote' => 'string',
'email_template_credit' => 'string',
'email_template_payment' => 'string',
'email_template_payment_partial' => 'string',
'email_subject_reminder1' => 'string',
'email_subject_reminder2' => 'string',
'email_subject_reminder3' => 'string',
'email_subject_reminder_endless' => 'string',
'email_template_reminder1' => 'string',
'email_template_reminder2' => 'string',
'email_template_reminder3' => 'string',
'email_template_reminder_endless' => 'string',
'inclusive_taxes' => 'bool',
'invoice_number_pattern' => 'string',
'invoice_number_counter' => 'integer',
'invoice_design_id' => 'string',
'document_email_attachment' => 'bool',
'enable_client_portal_password' => 'bool',
'enable_email_markup' => 'bool',
'enable_client_portal_dashboard' => 'bool',
'enable_client_portal' => 'bool',
'email_template_statement' => 'string',
'email_subject_statement' => 'string',
'signature_on_pdf' => 'bool',
'quote_footer' => 'string',
'page_size' => 'string',
'page_layout' => 'string',
'font_size' => 'int',
'primary_font' => 'string',
'secondary_font' => 'string',
'hide_paid_to_date' => 'bool',
'embed_documents' => 'bool',
'all_pages_header' => 'bool',
'all_pages_footer' => 'bool',
'project_number_pattern' => 'string',
'project_number_counter' => 'int',
'task_number_pattern' => 'string',
'task_number_counter' => 'int',
'expense_number_pattern' => 'string',
'expense_number_counter' => 'int',
'recurring_expense_number_pattern' => 'string',
'recurring_expense_number_counter' => 'int',
'recurring_quote_number_pattern' => 'string',
'recurring_quote_number_counter' => 'int',
'vendor_number_pattern' => 'string',
'vendor_number_counter' => 'int',
'ticket_number_pattern' => 'string',
'ticket_number_counter' => 'int',
'payment_number_pattern' => 'string',
'payment_number_counter' => 'int',
'reply_to_email' => 'string',
'bcc_email' => 'string',
'pdf_email_attachment' => 'bool',
'ubl_email_attachment' => 'bool',
'email_style' => 'string',
'email_style_custom' => 'string',
'company_gateway_ids' => 'string',
'address1' => 'string',
'address2' => 'string',
'city' => 'string',
'company_logo' => 'string',
'country_id' => 'string',
'client_number_pattern' => 'string',
'client_number_counter' => 'integer',
'credit_number_pattern' => 'string',
'credit_number_counter' => 'integer',
'currency_id' => 'string',
'custom_value1' => 'string',
'custom_value2' => 'string',
'custom_value3' => 'string',
'custom_value4' => 'string',
'custom_message_dashboard' => 'string',
'custom_message_unpaid_invoice' => 'string',
'custom_message_paid_invoice' => 'string',
'custom_message_unapproved_quote' => 'string',
'default_task_rate' => 'float',
'email_signature' => 'string',
'email_subject_invoice' => 'string',
'email_subject_quote' => 'string',
'email_subject_credit' => 'string',
'email_subject_payment' => 'string',
'email_subject_payment_partial' => 'string',
'email_template_invoice' => 'string',
'email_template_quote' => 'string',
'email_template_credit' => 'string',
'email_template_payment' => 'string',
'email_template_payment_partial' => 'string',
'email_subject_reminder1' => 'string',
'email_subject_reminder2' => 'string',
'email_subject_reminder3' => 'string',
'email_subject_reminder_endless' => 'string',
'email_template_reminder1' => 'string',
'email_template_reminder2' => 'string',
'email_template_reminder3' => 'string',
'email_template_reminder_endless' => 'string',
'inclusive_taxes' => 'bool',
'invoice_number_pattern' => 'string',
'invoice_number_counter' => 'integer',
'invoice_design_id' => 'string',
// 'invoice_fields' => 'string',
'invoice_taxes' => 'int',
'invoice_taxes' => 'int',
//'enabled_item_tax_rates' => 'int',
'invoice_footer' => 'string',
'invoice_labels' => 'string',
'invoice_terms' => 'string',
'credit_footer' => 'string',
'credit_terms' => 'string',
'name' => 'string',
'payment_terms' => 'string',
'payment_type_id' => 'string',
'phone' => 'string',
'postal_code' => 'string',
'quote_design_id' => 'string',
'credit_design_id' => 'string',
'quote_number_pattern' => 'string',
'quote_number_counter' => 'integer',
'quote_terms' => 'string',
'recurring_number_prefix' => 'string',
'reset_counter_frequency_id' => 'integer',
'reset_counter_date' => 'string',
'require_invoice_signature' => 'bool',
'require_quote_signature' => 'bool',
'state' => 'string',
'email' => 'string',
'vat_number' => 'string',
'id_number' => 'string',
'tax_name1' => 'string',
'tax_name2' => 'string',
'tax_name3' => 'string',
'tax_rate1' => 'float',
'tax_rate2' => 'float',
'tax_rate3' => 'float',
'show_accept_quote_terms' => 'bool',
'show_accept_invoice_terms' => 'bool',
'timezone_id' => 'string',
'valid_until' => 'string',
'date_format_id' => 'string',
'military_time' => 'bool',
'language_id' => 'string',
'show_currency_code' => 'bool',
'send_reminders' => 'bool',
'enable_client_portal_tasks' => 'bool',
'auto_archive_invoice' => 'bool',
'auto_archive_quote' => 'bool',
'auto_convert_quote' => 'bool',
'shared_invoice_quote_counter' => 'bool',
'counter_padding' => 'integer',
'invoice_footer' => 'string',
'invoice_labels' => 'string',
'invoice_terms' => 'string',
'credit_footer' => 'string',
'credit_terms' => 'string',
'name' => 'string',
'payment_terms' => 'string',
'payment_type_id' => 'string',
'phone' => 'string',
'postal_code' => 'string',
'quote_design_id' => 'string',
'credit_design_id' => 'string',
'quote_number_pattern' => 'string',
'quote_number_counter' => 'integer',
'quote_terms' => 'string',
'recurring_number_prefix' => 'string',
'reset_counter_frequency_id' => 'integer',
'reset_counter_date' => 'string',
'require_invoice_signature' => 'bool',
'require_quote_signature' => 'bool',
'state' => 'string',
'email' => 'string',
'vat_number' => 'string',
'id_number' => 'string',
'tax_name1' => 'string',
'tax_name2' => 'string',
'tax_name3' => 'string',
'tax_rate1' => 'float',
'tax_rate2' => 'float',
'tax_rate3' => 'float',
'show_accept_quote_terms' => 'bool',
'show_accept_invoice_terms' => 'bool',
'timezone_id' => 'string',
'valid_until' => 'string',
'date_format_id' => 'string',
'military_time' => 'bool',
'language_id' => 'string',
'show_currency_code' => 'bool',
'send_reminders' => 'bool',
'enable_client_portal_tasks' => 'bool',
'auto_archive_invoice' => 'bool',
'auto_archive_quote' => 'bool',
'auto_convert_quote' => 'bool',
'shared_invoice_quote_counter' => 'bool',
'counter_padding' => 'integer',
//'design' => 'string',
'website' => 'string',
'pdf_variables' => 'object',
'portal_custom_head' => 'string',
'portal_custom_css' => 'string',
'portal_custom_footer' => 'string',
'portal_custom_js' => 'string',
'client_portal_enable_uploads' => 'bool',
'purchase_order_number_counter' => 'integer',
'website' => 'string',
'pdf_variables' => 'object',
'portal_custom_head' => 'string',
'portal_custom_css' => 'string',
'portal_custom_footer' => 'string',
'portal_custom_js' => 'string',
'client_portal_enable_uploads' => 'bool',
'purchase_order_number_counter' => 'integer',
];
public static $free_plan_casts = [
'currency_id' => 'string',
'company_gateway_ids' => 'string',
'address1' => 'string',
'address2' => 'string',
'city' => 'string',
'company_logo' => 'string',
'country_id' => 'string',
'custom_value1' => 'string',
'custom_value2' => 'string',
'custom_value3' => 'string',
'custom_value4' => 'string',
'inclusive_taxes' => 'bool',
'name' => 'string',
'payment_terms' => 'string',
'payment_type_id' => 'string',
'phone' => 'string',
'postal_code' => 'string',
'state' => 'string',
'email' => 'string',
'vat_number' => 'string',
'id_number' => 'string',
'tax_name1' => 'string',
'tax_name2' => 'string',
'tax_name3' => 'string',
'tax_rate1' => 'float',
'tax_rate2' => 'float',
'tax_rate3' => 'float',
'timezone_id' => 'string',
'date_format_id' => 'string',
'military_time' => 'bool',
'language_id' => 'string',
'show_currency_code' => 'bool',
'website' => 'string',
'default_task_rate' => 'float',
'currency_id' => 'string',
'company_gateway_ids' => 'string',
'address1' => 'string',
'address2' => 'string',
'city' => 'string',
'company_logo' => 'string',
'country_id' => 'string',
'custom_value1' => 'string',
'custom_value2' => 'string',
'custom_value3' => 'string',
'custom_value4' => 'string',
'inclusive_taxes' => 'bool',
'name' => 'string',
'payment_terms' => 'string',
'payment_type_id' => 'string',
'phone' => 'string',
'postal_code' => 'string',
'state' => 'string',
'email' => 'string',
'vat_number' => 'string',
'id_number' => 'string',
'tax_name1' => 'string',
'tax_name2' => 'string',
'tax_name3' => 'string',
'tax_rate1' => 'float',
'tax_rate2' => 'float',
'tax_rate3' => 'float',
'timezone_id' => 'string',
'date_format_id' => 'string',
'military_time' => 'bool',
'language_id' => 'string',
'show_currency_code' => 'bool',
'website' => 'string',
'default_task_rate' => 'float',
];
/**
@ -848,7 +854,7 @@ class CompanySettings extends BaseSettings
$company_settings = (object) get_class_vars(self::class);
foreach ($company_settings as $key => $value) {
if (! property_exists($settings, $key)) {
if (!property_exists($settings, $key)) {
$settings->{$key} = self::castAttribute($key, $company_settings->{$key});
}
}
@ -881,7 +887,7 @@ class CompanySettings extends BaseSettings
{
$notification = new stdClass();
$notification->email = [];
$notification->email = ['invoice_sent_all','payment_success_all','payment_manual_all'];
$notification->email = ['invoice_sent_all', 'payment_success_all', 'payment_manual_all'];
return $notification;
}

View File

@ -213,7 +213,7 @@ class SettingsData
public bool $show_accept_quote_terms = false; //@TODO ben to confirm
public string $email_sending_method = 'default'; // enum 'default', 'gmail', 'office365', 'client_postmark', 'client_mailgun' //@implemented
public string $email_sending_method = 'default'; // enum 'default', 'gmail', 'office365', 'client_postmark', 'client_mailgun' , 'client_brevo' //@implemented
public string $gmail_sending_user_id = '0'; //@implemented
@ -433,6 +433,8 @@ class SettingsData
public string $mailgun_endpoint = 'api.mailgun.net'; // api.eu.mailgun.net
public string $brevo_secret = '';
public bool $auto_bill_standard_invoices = false;
public string $email_alignment = 'center'; // center, left, right
@ -469,8 +471,8 @@ class SettingsData
public function cast(mixed $object)
{
if(is_array($object)) {
$object = (object)$object;
if (is_array($object)) {
$object = (object) $object;
}
if (is_object($object)) {
@ -478,9 +480,9 @@ class SettingsData
try {
settype($object->{$key}, gettype($this->{$key}));
} catch(\Exception | \Error | \Throwable $e) {
} catch (\Exception | \Error | \Throwable $e) {
if(property_exists($this, $key)) {
if (property_exists($this, $key)) {
$object->{$key} = $this->{$key};
} else {
unset($object->{$key});
@ -506,11 +508,11 @@ class SettingsData
public function toObject(): object
{
return (object)$this->object;
return (object) $this->object;
}
public function toArray(): array
{
return (array)$this->object;
return (array) $this->object;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -138,7 +138,7 @@ class Handler extends ExceptionHandler
'email' => 'anonymous@example.com',
'name' => 'Anonymous User',
]);
} elseif (auth()->guard('user') && auth()->guard('user')->user() && auth()->user()->company() && auth()->user()->company()->account->report_errors) {
} elseif (auth()->guard('user') && auth()->guard('user')->user() && auth()->user()->companyIsSet() && auth()->user()->company()->account->report_errors) {
$scope->setUser([
'id' => auth()->user()->account->key,
'email' => 'anonymous@example.com',

View File

@ -294,6 +294,7 @@ class BaseExport
'line_total' => 'item.line_total',
'gross_line_total' => 'item.gross_line_total',
'tax_amount' => 'item.tax_amount',
'product_cost' => 'item.product_cost'
];
protected array $quote_report_keys = [

View File

@ -59,6 +59,9 @@ class DesignFilters extends QueryFilters
public function entities(string $entities = ''): Builder
{
if(stripos($entities, 'statement') !== false)
$entities = 'client';
if (strlen($entities) == 0 || str_contains($entities, ',')) {
return $this->builder;
}

View File

@ -328,7 +328,10 @@ class InvoiceFilters extends QueryFilters
}
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
// return $this->builder->orderByRaw('CAST(number AS UNSIGNED), number ' . $dir);
// return $this->builder->orderByRaw("number REGEXP '^[A-Za-z]+$',CAST(number as SIGNED INTEGER),CAST(REPLACE(number,'-','')AS SIGNED INTEGER) ,number");
// return $this->builder->orderByRaw('ABS(number) ' . $dir);
return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir);

View File

@ -52,6 +52,7 @@ class InvoiceSum
public InvoiceItemSum $invoice_items;
private $rappen_rounding = false;
/**
* Constructs the object with Invoice and Settings object.
*
@ -63,8 +64,11 @@ class InvoiceSum
if ($this->invoice->client) {
$this->precision = $this->invoice->client->currency()->precision;
$this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
} else {
$this->precision = $this->invoice->vendor->currency()->precision;
$this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
}
$this->tax_map = new Collection();
@ -252,11 +256,20 @@ class InvoiceSum
/* Set new calculated total */
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
if($this->rappen_rounding)
$this->invoice->amount = $this->roundRappen($this->invoice->amount);
$this->invoice->total_taxes = $this->getTotalTaxes();
return $this;
}
function roundRappen($value): float
{
return round($value / .05, 0) * .05;
}
public function getSubTotal()
{
return $this->sub_total;

View File

@ -47,6 +47,8 @@ class InvoiceSumInclusive
private $precision;
private $rappen_rounding = false;
public InvoiceItemSumInclusive $invoice_items;
/**
* Constructs the object with Invoice and Settings object.
@ -59,8 +61,10 @@ class InvoiceSumInclusive
if ($this->invoice->client) {
$this->precision = $this->invoice->client->currency()->precision;
$this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
} else {
$this->precision = $this->invoice->vendor->currency()->precision;
$this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
}
$this->tax_map = new Collection();
@ -268,13 +272,23 @@ class InvoiceSumInclusive
}
/* Set new calculated total */
/** @todo - rappen rounding here */
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
if($this->rappen_rounding) {
$this->invoice->amount = $this->roundRappen($this->invoice->amount);
}
$this->invoice->total_taxes = $this->getTotalTaxes();
return $this;
}
function roundRappen($value): float
{
return round($value / .05, 0) * .05;
}
public function getSubTotal()
{
return $this->sub_total;

View File

@ -159,7 +159,7 @@ class SwissQrGenerator
// Optionally, add some human-readable information about what the bill is for.
$qrBill->setAdditionalInformation(
QrBill\DataGroup\Element\AdditionalInformation::create(
$this->invoice->public_notes ? substr($this->invoice->public_notes, 0, 139) : ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number])
$this->invoice->public_notes ? substr(strip_tags($this->invoice->public_notes), 0, 139) : ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number])
)
);

View File

@ -0,0 +1,72 @@
<?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\Http\Controllers;
use App\Jobs\Brevo\ProcessBrevoWebhook;
use Illuminate\Http\Request;
/**
* Class PostMarkController.
*/
class BrevoController extends BaseController
{
private $invitation;
public function __construct()
{
}
/**
* Process Postmark Webhook.
*
*
* @OA\Post(
* path="/api/v1/postmark_webhook",
* operationId="postmarkWebhook",
* tags={"postmark"},
* summary="Processing webhooks from PostMark",
* description="Adds an credit to the system",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Returns the saved credit object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Credit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function webhook(Request $request)
{
if ($request->has('token') && $request->get('token') == config('services.brevo.key')) {
ProcessBrevoWebhook::dispatch($request->all())->delay(10);
return response()->json(['message' => 'Success'], 200);
}
return response()->json(['message' => 'Unauthorized'], 403);
}
}

View File

@ -45,7 +45,7 @@ class EmailPreferencesController extends Controller
if ($invitation->contact->is_locked && !Cache::has("unsubscribe_notitfication_suppression:{$invitation_key}")) {
$nmo = new NinjaMailerObject();
$nmo->mailable = new NinjaMailer((new ClientUnsubscribedObject($invitation->contact, $invitation->contact->company, $invitation->contact->company->owner()->company_users()->first()->portalType() ?? true))->build());
$nmo->mailable = new NinjaMailer((new ClientUnsubscribedObject($invitation->contact, $invitation->contact->company, true))->build());
$nmo->company = $invitation->contact->company;
$nmo->to_user = $invitation->contact->company->owner();
$nmo->settings = $invitation->contact->company->settings;

View File

@ -271,6 +271,7 @@ class InvitationController extends Controller
->with('contact.client')
->firstOrFail();
if ($invitation->contact->trashed()) {
$invitation->contact->restore();
}
@ -294,7 +295,10 @@ class InvitationController extends Controller
'payable_invoices' => [
['invoice_id' => $invitation->invoice->hashed_id, 'amount' => $amount],
],
'signature' => false
'signature' => false,
'contact_first_name' => $invitation->contact->first_name ?? '',
'contact_last_name' => $invitation->contact->last_name ?? '',
'contact_email' => $invitation->contact->email ?? ''
];
$request->replace($data);

View File

@ -11,26 +11,29 @@
namespace App\Http\Controllers;
use App\Models\Client;
use App\Libraries\MultiDB;
use Illuminate\Http\Response;
use App\Models\CompanyGateway;
use App\Utils\Traits\MakesHash;
use App\DataMapper\FeesAndLimits;
use App\Jobs\Util\ApplePayDomain;
use Illuminate\Support\Facades\Cache;
use App\Factory\CompanyGatewayFactory;
use App\Filters\CompanyGatewayFilters;
use App\Repositories\CompanyRepository;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Transformers\CompanyGatewayTransformer;
use App\PaymentDrivers\Stripe\Jobs\StripeWebhook;
use App\PaymentDrivers\CheckoutCom\CheckoutSetupWebhook;
use App\Http\Requests\CompanyGateway\BulkCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\CreateCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\DestroyCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\EditCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\ShowCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\TestCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\StoreCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\CreateCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest;
use App\Jobs\Util\ApplePayDomain;
use App\Models\Client;
use App\Models\CompanyGateway;
use App\PaymentDrivers\CheckoutCom\CheckoutSetupWebhook;
use App\PaymentDrivers\Stripe\Jobs\StripeWebhook;
use App\Repositories\CompanyRepository;
use App\Transformers\CompanyGatewayTransformer;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Response;
use App\Http\Requests\CompanyGateway\DestroyCompanyGatewayRequest;
/**
* Class CompanyGatewayController.
@ -52,6 +55,9 @@ class CompanyGatewayController extends BaseController
private string $checkout_key = '3758e7f7c6f4cecf0f4f348b9a00f456';
private string $forte_key = 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs';
/**
* CompanyGatewayController constructor.
* @param CompanyRepository $company_repo
@ -225,6 +231,13 @@ class CompanyGatewayController extends BaseController
StripeWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id);
} elseif($company_gateway->gateway_key == $this->checkout_key) {
CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id);
} elseif($company_gateway->gateway_key == $this->forte_key) {
dispatch(function () use ($company_gateway) {
MultiDB::setDb($company_gateway->company->db);
$company_gateway->driver()->updateFees();
})->afterResponse();
}
return $this->itemResponse($company_gateway);
@ -407,6 +420,13 @@ class CompanyGatewayController extends BaseController
if($company_gateway->gateway_key == $this->checkout_key) {
CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->fresh()->id);
}elseif($company_gateway->gateway_key == $this->forte_key){
dispatch(function () use ($company_gateway) {
MultiDB::setDb($company_gateway->company->db);
$company_gateway->driver()->updateFees();
})->afterResponse();
}
return $this->itemResponse($company_gateway);
@ -535,4 +555,28 @@ class CompanyGatewayController extends BaseController
return $this->listResponse(CompanyGateway::withTrashed()->company()->whereIn('id', $request->ids));
}
public function test(TestCompanyGatewayRequest $request, CompanyGateway $company_gateway)
{
return response()->json(['message' => $company_gateway->driver()->auth() ? 'true' : 'false'], 200);
}
public function importCustomers(TestCompanyGatewayRequest $request, CompanyGateway $company_gateway)
{
//Throttle here
// if (Cache::get("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}"))
// return response()->json(['message' => ctrans('texts.import_started')], 200);
dispatch(function () use($company_gateway) {
MultiDB::setDb($company_gateway->company->db);
$company_gateway->driver()->importCustomers();
})->afterResponse();
Cache::put("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}", true, 300);
return response()->json(['message' => ctrans('texts.import_started')], 200);
}
}

View File

@ -158,7 +158,6 @@ class LicenseController extends BaseController
/* Catch claim license requests */
if (config('ninja.environment') == 'selfhost') {
// $response = Http::get( "http://ninja.test:8000/claim_license", [
$response = Http::get("https://invoicing.co/claim_license", [
'license_key' => $license_key,
'product_id' => 3,

View File

@ -63,7 +63,11 @@ class LogoutController extends BaseController
$ct->company
->tokens()
->where('is_system', true)
->forceDelete();
->cursor()
->each(function ($ct){
$ct->token = \Illuminate\Support\Str::random(64);
$ct->save();
});
return response()->json(['message' => 'All tokens deleted'], 200);
}

View File

@ -55,7 +55,17 @@ class SmtpController extends BaseController
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
try {
Mail::to($user->email, $user->present()->name())->send(new TestMailServer('Email Server Works!', strlen($company->settings->custom_sending_email) > 1 ? $company->settings->custom_sending_email : $user->email));
$sending_email = (isset($company->settings->custom_sending_email) && stripos($company->settings->custom_sending_email, "@")) ? $company->settings->custom_sending_email : $user->email;
$sending_user = (isset($company->settings->email_from_name) && strlen($company->settings->email_from_name) > 2) ? $company->settings->email_from_name : $user->name();
$mailable = new TestMailServer('Email Server Works!', $sending_email);
$mailable->from($sending_email,$sending_user);
Mail::mailer('smtp')
->to($user->email, $user->present()->name())
->send($mailable);
} catch (\Exception $e) {
app('mail.manager')->forgetMailers();
return response()->json(['message' => $e->getMessage()], 400);

View File

@ -54,8 +54,6 @@ class StripeConnectController extends BaseController
$redirect_uri = config('ninja.app_url').'/stripe/completed';
$endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_client_id}&redirect_uri={$redirect_uri}&scope=read_write&state={$token}";
\Illuminate\Support\Facades\Cache::pull($token);
return redirect($endpoint);
}
@ -156,6 +154,8 @@ class StripeConnectController extends BaseController
$redirect_uri = config('ninja.app_url');
}
\Illuminate\Support\Facades\Cache::pull($request->token);
//response here
return view('auth.connect.completed', ['url' => $redirect_uri]);
// return redirect($redirect_uri);

View File

@ -135,7 +135,7 @@ class Kernel extends HttpKernel
'can' => Authorize::class,
'cors' => Cors::class,
'guest' => RedirectIfAuthenticated::class,
'signed' => ValidateSignature::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'verified' => EnsureEmailIsVerified::class,
'query_logging' => QueryLogging::class,
'token_auth' => TokenAuth::class,

View File

@ -33,15 +33,15 @@ class UploadBankIntegrationRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -33,15 +33,15 @@ class UploadBankTransactionRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -45,32 +45,18 @@ class StoreClientRequest extends Request
$user = auth()->user();
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
}
if (isset($this->number)) {
$rules['number'] = Rule::unique('clients')->where('company_id', $user->company()->id);
}
$rules['country_id'] = 'integer|nullable';
if (isset($this->currency_code)) {
$rules['currency_code'] = 'sometimes|exists:currencies,code';
}
if (isset($this->country_code)) {
$rules['country_code'] = new CountryCodeExistsRule();
$rules['file'] = $this->fileValidation();
}
/* Ensure we have a client name, and that all emails are unique*/
@ -97,6 +83,9 @@ class StoreClientRequest extends Request
$rules['number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)];
$rules['id_number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)];
$rules['classification'] = 'bail|sometimes|nullable|in:individual,business,company,partnership,trust,charity,government,other';
$rules['shipping_country_id'] = 'integer|nullable|exists:countries,id';
$rules['number'] = ['sometimes', 'nullable', 'bail', Rule::unique('clients')->where('company_id', $user->company()->id)];
$rules['country_id'] = 'integer|nullable|exists:countries,id';
return $rules;
}
@ -139,12 +128,16 @@ class StoreClientRequest extends Request
if (! array_key_exists('currency_id', $input['settings']) && isset($input['group_settings_id'])) {
$group_settings = GroupSetting::find($input['group_settings_id']);
if ($group_settings && property_exists($group_settings->settings, 'currency_id') && isset($group_settings->settings->currency_id)) {
if ($group_settings && property_exists($group_settings->settings, 'currency_id') && is_numeric($group_settings->settings->currency_id)) {
$input['settings']['currency_id'] = (string) $group_settings->settings->currency_id;
} else {
$input['settings']['currency_id'] = (string) $user->company()->settings->currency_id;
}
} elseif (! array_key_exists('currency_id', $input['settings'])) {
}
elseif (! array_key_exists('currency_id', $input['settings'])) {
$input['settings']['currency_id'] = (string) $user->company()->settings->currency_id;
}
elseif (empty($input['settings']['currency_id']) ?? true) {
$input['settings']['currency_id'] = (string) $user->company()->settings->currency_id;
}
@ -160,10 +153,13 @@ class StoreClientRequest extends Request
}
}
// allow setting country_id by iso code
if (isset($input['country_code'])) {
$input['country_id'] = $this->getCountryCode($input['country_code']);
}
// allow setting country_id by iso code
if (isset($input['shipping_country_code'])) {
$input['shipping_country_id'] = $this->getCountryCode($input['shipping_country_code']);
}
@ -173,10 +169,14 @@ class StoreClientRequest extends Request
unset($input['number']);
}
// prevent xss injection
if (array_key_exists('name', $input)) {
$input['name'] = strip_tags($input['name']);
}
//If you want to validate, the prop must be set.
$input['id'] = null;
$this->replace($input);
}

View File

@ -44,15 +44,15 @@ class UpdateClientRequest extends Request
$user = auth()->user();
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
} else {
$rules['documents'] = 'bail|sometimes|array';
}
@ -60,17 +60,11 @@ class UpdateClientRequest extends Request
$rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000';
$rules['industry_id'] = 'integer|nullable';
$rules['size_id'] = 'integer|nullable';
$rules['country_id'] = 'integer|nullable';
$rules['shipping_country_id'] = 'integer|nullable';
$rules['country_id'] = 'integer|nullable|exists:countries,id';
$rules['shipping_country_id'] = 'integer|nullable|exists:countries,id';
$rules['classification'] = 'bail|sometimes|nullable|in:individual,business,company,partnership,trust,charity,government,other';
if ($this->id_number) {
$rules['id_number'] = Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id);
}
if ($this->number) {
$rules['number'] = Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id);
}
$rules['id_number'] = ['sometimes', 'bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)];
$rules['number'] = ['sometimes', 'bail', Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id)];
$rules['settings'] = new ValidClientGroupSettingsRule();
$rules['contacts'] = 'array';
@ -112,6 +106,9 @@ class UpdateClientRequest extends Request
if (array_key_exists('settings', $input) && ! array_key_exists('currency_id', $input['settings'])) {
$input['settings']['currency_id'] = (string) $user->company()->settings->currency_id;
}
elseif (empty($input['settings']['currency_id']) ?? true) {
$input['settings']['currency_id'] = (string) $user->company()->settings->currency_id;
}
if (isset($input['language_code'])) {
$input['settings']['language_id'] = $this->getLanguageId($input['language_code']);
@ -127,9 +124,35 @@ class UpdateClientRequest extends Request
$input['name'] = strip_tags($input['name']);
}
// allow setting country_id by iso code
if (isset($input['country_code'])) {
$input['country_id'] = $this->getCountryCode($input['country_code']);
}
// allow setting country_id by iso code
if (isset($input['shipping_country_code'])) {
$input['shipping_country_id'] = $this->getCountryCode($input['shipping_country_code']);
}
$this->replace($input);
}
private function getCountryCode($country_code)
{
$countries = Cache::get('countries');
$country = $countries->filter(function ($item) use ($country_code) {
return $item->iso_3166_2 == $country_code || $item->iso_3166_3 == $country_code;
})->first();
if ($country) {
return (string) $country->id;
}
return '';
}
private function getLanguageId($language_code)
{
$languages = Cache::get('languages');

View File

@ -33,15 +33,15 @@ class UploadClientRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -30,15 +30,15 @@ class UploadCompanyRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $rules;

View File

@ -0,0 +1,49 @@
<?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\Http\Requests\CompanyGateway;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class TestCompanyGatewayRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin();
}
public function rules()
{
return [
];
}
public function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -47,17 +47,17 @@ class StoreCreditRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
/** @var \App\Models\User $user */

View File

@ -49,17 +49,17 @@ class UpdateCreditRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('credits')->where('company_id', $user->company()->id)->ignore($this->credit->id)];

View File

@ -33,15 +33,15 @@ class UploadCreditRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -33,15 +33,15 @@ class UploadExpenseRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -30,15 +30,15 @@ class UploadGroupSettingRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $rules;

View File

@ -44,17 +44,17 @@ class StoreInvoiceRequest extends Request
$user = auth()->user();
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0';

View File

@ -46,17 +46,17 @@ class UpdateInvoiceRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
// $rules['id'] = new LockedInvoiceRule($this->invoice);

View File

@ -33,15 +33,15 @@ class UploadInvoiceRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -123,17 +123,17 @@ class StorePaymentRequest extends Request
];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $rules;

View File

@ -52,17 +52,17 @@ class UpdatePaymentRequest extends Request
}
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $rules;

View File

@ -33,15 +33,15 @@ class UploadPaymentRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -32,17 +32,17 @@ class StoreProductRequest extends Request
public function rules()
{
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['cost'] = 'sometimes|numeric';

View File

@ -35,17 +35,17 @@ class UpdateProductRequest extends Request
public function rules()
{
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['cost'] = 'numeric';

View File

@ -32,15 +32,15 @@ class UploadProductRequest extends Request
{
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -51,17 +51,17 @@ class StoreProjectRequest extends Request
}
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $this->globalRules($rules);

View File

@ -48,17 +48,17 @@ class UpdateProjectRequest extends Request
$rules['budgeted_hours'] = 'sometimes|numeric';
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $this->globalRules($rules);

View File

@ -33,15 +33,15 @@ class UploadProjectRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -54,17 +54,17 @@ class StorePurchaseOrderRequest extends Request
$rules['line_items'] = 'array';
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
} else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['status_id'] = 'nullable|integer|in:1,2,3,4,5';

View File

@ -56,17 +56,17 @@ class UpdatePurchaseOrderRequest extends Request
$rules['is_amount_discount'] = ['boolean'];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['status_id'] = 'sometimes|integer|in:1,2,3,4,5';

View File

@ -33,15 +33,15 @@ class UploadPurchaseOrderRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -46,17 +46,17 @@ class StoreQuoteRequest extends Request
$rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id;
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)];

View File

@ -43,17 +43,17 @@ class UpdateQuoteRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}

View File

@ -33,15 +33,15 @@ class UploadQuoteRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -57,15 +57,15 @@ class StoreRecurringExpenseRequest extends Request
$rules['currency_id'] = 'bail|required|integer|exists:currencies,id';
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $this->globalRules($rules);

View File

@ -49,15 +49,15 @@ class UpdateRecurringExpenseRequest extends Request
$rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $this->globalRules($rules);

View File

@ -30,15 +30,15 @@ class UploadRecurringExpenseRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $rules;

View File

@ -46,17 +46,17 @@ class StoreRecurringInvoiceRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id;

View File

@ -45,17 +45,17 @@ class UpdateRecurringInvoiceRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['number'] = ['bail', 'sometimes', Rule::unique('recurring_invoices')->where('company_id', $user->company()->id)->ignore($this->recurring_invoice->id)];

View File

@ -33,15 +33,15 @@ class UploadRecurringInvoiceRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -46,15 +46,15 @@ class StoreRecurringQuoteRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id;

View File

@ -38,15 +38,15 @@ class UpdateRecurringQuoteRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
if ($this->number) {

View File

@ -30,15 +30,15 @@ class UploadRecurringQuoteRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $rules;

View File

@ -20,7 +20,7 @@ class Request extends FormRequest
use MakesHash;
use RuntimeFormRequest;
protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp,xml,zip,csv,ods,odt,odp|max:100000';
protected $file_validation = 'sometimes|file|max:100000|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp,xml,zip,csv,ods,odt,odp';
/**
* Get the validation rules that apply to the request.
*
@ -31,6 +31,15 @@ class Request extends FormRequest
return [];
}
public function fileValidation()
{
if(config('ninja.upload_extensions'))
return $this->file_validation. ",".config('ninja.upload_extensions');
return $this->file_validation;
}
public function globalRules($rules)
{
$merge_rules = [];

View File

@ -79,17 +79,17 @@ class StoreTaskRequest extends Request
}];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}

View File

@ -85,17 +85,17 @@ class UpdateTaskRequest extends Request
}];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
return $this->globalRules($rules);

View File

@ -33,15 +33,15 @@ class UploadTaskRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -95,6 +95,8 @@ class StoreUserRequest extends Request
$input['last_name'] = strip_tags($input['last_name']);
}
$input['id'] = null;
$this->replace($input);
}

View File

@ -61,17 +61,17 @@ class StoreVendorRequest extends Request
$rules['currency_id'] = 'bail|required|exists:currencies,id';
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id';

View File

@ -62,17 +62,17 @@ class UpdateVendorRequest extends Request
$rules['currency_id'] = 'bail|sometimes|exists:currencies,id';
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id';

View File

@ -30,15 +30,15 @@ class UploadVendorRequest extends Request
$rules = [];
if ($this->file('documents') && is_array($this->file('documents'))) {
$rules['documents.*'] = $this->file_validation;
$rules['documents.*'] = $this->fileValidation();
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
$rules['documents'] = $this->fileValidation();
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;
$rules['file.*'] = $this->fileValidation();
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
$rules['file'] = $this->fileValidation();
}
$rules['is_public'] = 'sometimes|boolean';

View File

@ -5,7 +5,7 @@
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
*1`
* @license https://www.elastic.co/licensing/elastic-license
*/
@ -21,6 +21,7 @@ class BlackListRule implements ValidationRule
{
/** Bad domains +/- dispoable email domains */
private array $blacklist = [
'wireconnected.com',
'secure-coinspot.com',
'casasotombo.com',
'otpku.com',

View File

@ -98,19 +98,19 @@ class InvoiceTransformer extends BaseTransformer
$invoice_data,
'invoice.partial_due_date'
),
'custom_surcharge1' => $this->getString(
'custom_surcharge1' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge1'
),
'custom_surcharge2' => $this->getString(
'custom_surcharge2' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge2'
),
'custom_surcharge3' => $this->getString(
'custom_surcharge3' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge3'
),
'custom_surcharge4' => $this->getString(
'custom_surcharge4' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge4'
),

View File

@ -85,6 +85,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue
$this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja();
sleep(5);
throw $e;
}
if (!$this->nordigen_account) {

View File

@ -0,0 +1,492 @@
<?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\Jobs\Brevo;
use App\DataMapper\Analytics\Mail\EmailBounce;
use App\DataMapper\Analytics\Mail\EmailSpam;
use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation;
use App\Models\PurchaseOrderInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Models\SystemLog;
use App\Notifications\Ninja\EmailBounceNotification;
use App\Notifications\Ninja\EmailSpamNotification;
use Brevo\Client\Configuration;
use Brevo\Client\Model\GetTransacEmailContentEvents;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Brevo\Client\Api\TransactionalEmailsApi;
use Turbo124\Beacon\Facades\LightLogs;
class ProcessBrevoWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $invitation;
private $entity;
private array $default_response = [
'recipients' => '',
'subject' => 'Message not found.',
'entity' => '',
'entity_id' => '',
'events' => [],
];
private ?Company $company = null;
/**
* Create a new job instance.
*
*/
public function __construct(private array $request)
{
}
private function getSystemLog(string $message_id): ?SystemLog
{
return SystemLog::query()
->where('company_id', $this->invitation->company_id)
->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE)
->whereJsonContains('log', ['message-id' => $message_id])
->orderBy('id', 'desc')
->first();
}
private function updateSystemLog(SystemLog $system_log, array $data): void
{
$system_log->log = $data;
$system_log->save();
}
/**
* Execute the job.
*
*
* @return void
*/
public function handle()
{
MultiDB::findAndSetDbByCompanyKey($this->request['tags'][0]);
$this->company = Company::where('company_key', $this->request['tags'][0])->first();
$this->invitation = $this->discoverInvitation($this->request['message-id']);
if ($this->company && $this->request['event'] == 'spam' && config('ninja.notification.slack')) {
$this->company->notification(new EmailSpamNotification($this->company))->ninja();
}
if (!$this->invitation) {
return;
}
if (array_key_exists('reason', $this->request)) {
$this->invitation->email_error = $this->request['reason'];
}
switch ($this->request['event']) {
case 'delivered':
return $this->processDelivery();
case 'soft_bounce':
case 'hard_bounce':
case 'invalid_email':
case 'blocked':
if ($this->request['subject'] == ctrans('texts.confirmation_subject')) {
$this->company->notification(new EmailBounceNotification($this->request['email']))->ninja();
}
return $this->processBounce();
case 'spam':
return $this->processSpamComplaint();
case 'unique_opened':
case 'opened':
case 'click':
return $this->processOpen();
default:
# code...
break;
}
}
// {
// "id": 948562,
// "email": "test@example.com",
// "message-id": "<202312211546.94160606300@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:34:42",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "unique_opened",
// "subject": "Reminder: Invoice 0002 from Untitled Company",
// "sending_ip": "74.125.208.8",
// "ts": 1703180082,
// "ts_epoch": 1703180082286,
// "ts_event": 1703180082,
// "link": "",
// "sender_email": "user@example.com"
// }
// {
// "id": 948562,
// "email": "test@example.com",
// "message-id": "<202312211555.14720890391@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:34:53",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "opened",
// "subject": "Reminder: Invoice 0002 from Untitled Company",
// "sending_ip": "74.125.208.8",
// "ts": 1703180093,
// "ts_epoch": 1703180093075,
// "ts_event": 1703180093,
// "link": "",
// "sender_email": "user@example.com"
// }
// {
// "id": 948562,
// "email": "paul@wer-ner.de",
// "message-id": "<202312280812.10968711117@smtp-relay.mailin.fr>",
// "date": "2023-12-28 09:20:18",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "click",
// "subject": "Reminder: Invoice 0002 from Untitled Company",
// "sending_ip": "79.235.133.157",
// "ts": 1703751618,
// "ts_epoch": 1703751618831,
// "ts_event": 1703751618,
// "link": "http://localhost/client/invoice/CssCvqOcKsenMCgYJ7EUNRZwxSDGUkau",
// "sender_email": "user@example.com"
// }
private function processOpen()
{
$this->invitation->opened_date = now();
$this->invitation->save();
$data = array_merge($this->request, ['history' => $this->fetchMessage()]);
$sl = $this->getSystemLog($this->request['message-id']);
if ($sl) {
$this->updateSystemLog($sl, $data);
return;
}
(
new SystemLogger(
$data,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_OPENED,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->invitation->contact->client,
$this->invitation->company
)
)->handle();
}
// {
// "id": 948562,
// "email": "test@example",
// "message-id": "<202312211742.12697514322@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:42:31",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "delivered",
// "subject": "Reminder: Invoice 0002 from Untitled Company",
// "sending_ip": "77.32.148.26",
// "ts_event": 1703180551,
// "ts": 1703180551,
// "reason": "sent",
// "ts_epoch": 1703180551324,
// "sender_email": "user@example.com"
// }
private function processDelivery()
{
$this->invitation->email_status = 'delivered';
$this->invitation->save();
$data = array_merge($this->request, ['history' => $this->fetchMessage()]);
$sl = $this->getSystemLog($this->request['message-id']);
if ($sl) {
$this->updateSystemLog($sl, $data);
return;
}
(
new SystemLogger(
$data,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_DELIVERY,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->invitation->contact->client,
$this->invitation->company
)
)->handle();
}
// {
// "id": 948562,
// "email": "ryder36@example.net",
// "message-id": "<202312211744.55168080257@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:44:52",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "soft_bounce",
// "subject": "Reminder: Invoice 0001 from Untitled Company",
// "sending_ip": "77.32.148.26",
// "ts_event": 1703180692,
// "ts": 1703180692,
// "reason": "Unable to find MX of domain example.net",
// "ts_epoch": 1703180692382,
// "sender_email": "user@example.com"
// }
// {
// "id": 948562,
// "email": "gloria46@example.com",
// "message-id": "<202312211744.57456703957@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:44:54",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "hard_bounce",
// "subject": "Reminder: Invoice 0001 from Untitled Company",
// "sending_ip": "77.32.148.25",
// "ts_event": 1703180694,
// "ts": 1703180694,
// "reason": "blocked by Admin",
// "ts_epoch": 1703180694175,
// "sender_email": "user@example.com"
// }
// {
// "event" : "invalid_email",
// "email" : "example@example.com",
// "id" : 1,
// "date" : "yyyy-mm-dd hh:i:s",
// "message-id" : "<xxx@msgid.domain>",
// "subject" : "Test subject",
// "tag" : "<defined-tag>",//json of array
// "tags": [
// "company_key"
// ],
// "sending_ip" : "xxx.xx.xxx.xx",
// "ts_epoch" : 1534486682000,
// "template_id" : 1,
// "sender_email": "user@example.com",
// }
// {
// "id": 948562,
// "email": "neoma.langosh@example.com",
// "message-id": "<202312211745.65538701430@smtp-relay.mailin.fr>",
// "date": "2023-12-21 18:45:48",
// "tags": [
// "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV"
// ],
// "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]",
// "event": "blocked",
// "subject": "Reminder: Invoice 0001 from Untitled Company",
// "ts_event": 1703180748,
// "ts": 1703180748,
// "reason": "blocked : due to blacklist user",
// "ts_epoch": 1703180748987,
// "sender_email": "user@example.com"
// }
private function processBounce()
{
$this->invitation->email_status = 'bounced';
$this->invitation->save();
$bounce = new EmailBounce(
$this->request['tags'][0],
$this->request['sender_email'], // TODO: @turbo124 is this the recipent?
$this->request['message-id']
);
LightLogs::create($bounce)->send();
$data = array_merge($this->request, ['history' => $this->fetchMessage()]);
$sl = $this->getSystemLog($this->request['message-id']);
if ($sl) {
$this->updateSystemLog($sl, $data);
return;
}
(new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle();
// if(config('ninja.notification.slack'))
// $this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja();
}
// {
// "event" : "spam",
// "email" : "example@example.com",
// "id" : 1,
// "date" : "yyyy-mm-dd hh:i:s",
// "message-id" : "<xxx@msgid.domain>",
// "tag" : "<defined-tag>",//json of array
// "tags": [
// "company_key"
// ],
// "sending_ip" : "xxx.xx.xxx.xx",
// "sender_email": "user@example.com",
// }
private function processSpamComplaint()
{
$this->invitation->email_status = 'spam';
$this->invitation->save();
$spam = new EmailSpam(
$this->request['tags'][0],
$this->request['sender_email'],
$this->request['message-id']
);
LightLogs::create($spam)->send();
$data = array_merge($this->request, ['history' => $this->fetchMessage()]);
$sl = $this->getSystemLog($this->request['message-id']);
if ($sl) {
$this->updateSystemLog($sl, $data);
return;
}
(new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle();
if (config('ninja.notification.slack')) {
$this->invitation->company->notification(new EmailSpamNotification($this->invitation->company->account))->ninja();
}
}
private function discoverInvitation(string $message_id)
{
$invitation = false;
if ($invitation = InvoiceInvitation::where('message_id', $message_id)->first()) {
$this->entity = 'invoice';
return $invitation;
} elseif ($invitation = QuoteInvitation::where('message_id', $message_id)->first()) {
$this->entity = 'quote';
return $invitation;
} elseif ($invitation = RecurringInvoiceInvitation::where('message_id', $message_id)->first()) {
$this->entity = 'recurring_invoice';
return $invitation;
} elseif ($invitation = CreditInvitation::where('message_id', $message_id)->first()) {
$this->entity = 'credit';
return $invitation;
} elseif ($invitation = PurchaseOrderInvitation::where('message_id', $message_id)->first()) {
$this->entity = 'purchase_order';
return $invitation;
} else {
return $invitation;
}
}
public function getRawMessage(string $message_id)
{
$brevo_secret = !empty($this->company->settings->brevo_secret) ? $this->company->settings->brevo_secret : config('services.brevo.key');
$brevo = new TransactionalEmailsApi(null, Configuration::getDefaultConfiguration()->setApiKey('api-key', $brevo_secret));
$messageDetail = $brevo->getTransacEmailContent($message_id);
return $messageDetail;
}
public function getBounceId(string $message_id): ?int
{
$messageDetail = $this->getRawMessage($message_id);
$event = collect($messageDetail->getEvents())->first(function ($event) {
return $event?->Details?->BounceID ?? false;
});
return $event?->Details?->BounceID ?? null;
}
// TODO
private function fetchMessage(): array
{
if (strlen($this->request['message-id']) < 1) {
return $this->default_response;
}
try {
$messageDetail = $this->getRawMessage($this->request['message-id']);
$recipient = array_key_exists("email", $this->request) ? $this->request["email"] : '';
$server_ip = array_key_exists("sending_ip", $this->request) ? $this->request["sending_ip"] : '';
$delivery_message = array_key_exists("reason", $this->request) ? $this->request["reason"] : '';
$subject = $messageDetail->getSubject() ?? '';
$events = collect($messageDetail->getEvents())->map(function (GetTransacEmailContentEvents $event) use ($recipient, $server_ip, $delivery_message) { // @turbo124 event does only contain name & time property, how to handle transformation?!
return [
'bounce_id' => '',
'recipient' => $recipient,
'status' => $event->name ?? '',
'delivery_message' => $delivery_message, // TODO: @turbo124 this results in all cases for the history in the string, which may be incorrect
'server' => '',
'server_ip' => $server_ip,
'date' => \Carbon\Carbon::parse($event->getTime())->format('Y-m-d H:i:s') ?? '',
];
})->toArray();
return [
'recipients' => $recipient,
'subject' => $subject,
'entity' => $this->entity ?? '',
'entity_id' => $this->invitation->{$this->entity}->hashed_id ?? '',
'events' => $events,
];
} catch (\Exception $e) {
return $this->default_response;
}
}
}

View File

@ -311,8 +311,11 @@ class CompanyImport implements ShouldQueue
}
}
unlink($tmp_file);
unlink(Storage::path($this->file_location));
if(file_exists($tmp_file))
unlink($tmp_file);
if(Storage::exists($this->file_location))
unlink(Storage::path($this->file_location));
}
//

View File

@ -62,6 +62,7 @@ class NinjaMailerJob implements ShouldQueue
protected $client_mailgun_domain = false;
protected $client_brevo_secret = false;
public function __construct(public ?NinjaMailerObject $nmo, public bool $override = false)
{
@ -99,7 +100,7 @@ class NinjaMailerJob implements ShouldQueue
}
$this->nmo->mailable->replyTo($this->nmo->settings->reply_to_email, $reply_to_name);
} elseif(isset($this->nmo->invitation->user)) {
} elseif (isset ($this->nmo->invitation->user)) {
$this->nmo->mailable->replyTo($this->nmo->invitation->user->email, $this->nmo->invitation->user->present()->name());
} else {
$this->nmo->mailable->replyTo($this->company->owner()->email, $this->company->owner()->present()->name());
@ -112,16 +113,16 @@ class NinjaMailerJob implements ShouldQueue
/* If we have an invitation present, we pass the invitation key into the email headers*/
if ($this->nmo->invitation) {
$this->nmo
->mailable
->withSymfonyMessage(function ($message) {
$message->getHeaders()->addTextHeader('x-invitation', $this->nmo->invitation->key);
});
->mailable
->withSymfonyMessage(function ($message) {
$message->getHeaders()->addTextHeader('x-invitation', $this->nmo->invitation->key);
});
}
//send email
try {
nlog("Trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString());
nlog("Using mailer => ". $this->mailer);
nlog("Trying to send to {$this->nmo->to_user->email} " . now()->toDateTimeString());
nlog("Using mailer => " . $this->mailer);
$mailer = Mail::mailer($this->mailer);
@ -133,10 +134,14 @@ class NinjaMailerJob implements ShouldQueue
$mailer->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain, $this->nmo->settings->mailgun_endpoint);
}
if ($this->client_brevo_secret) {
$mailer->brevo_config($this->client_brevo_secret);
}
$mailable = $this->nmo->mailable;
/** May need to re-build it here */
if(Ninja::isHosted() && method_exists($mailable, 'build')) {
if (Ninja::isHosted() && method_exists($mailable, 'build')) {
$mailable->build();
}
@ -145,18 +150,19 @@ class NinjaMailerJob implements ShouldQueue
->send($mailable);
/* Count the amount of emails sent across all the users accounts */
Cache::increment("email_quota".$this->company->account->key);
$this->incrementEmailCounter();
LightLogs::create(new EmailSuccess($this->nmo->company->company_key, $this->nmo->mailable->subject))
->send();
->send();
} catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) {
} catch (\Symfony\Component\Mime\Exception\RfcComplianceException $e) {
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
$this->fail();
$this->cleanUpMailers();
$this->logMailError($e->getMessage(), $this->company->clients()->first());
return;
} catch(\Symfony\Component\Mime\Exception\LogicException $e) {
} catch (\Symfony\Component\Mime\Exception\LogicException $e) {
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
$this->fail();
$this->cleanUpMailers();
@ -221,6 +227,12 @@ class NinjaMailerJob implements ShouldQueue
$this->cleanUpMailers();
}
private function incrementEmailCounter(): void
{
if(in_array($this->mailer, ['default','mailgun','postmark']))
Cache::increment("email_quota".$this->company->account->key);
}
/**
* Entity notification when an email fails to send
*
@ -266,7 +278,7 @@ class NinjaMailerJob implements ShouldQueue
// return $this;
// }
if(Ninja::isHosted() && $this->company->account->isPaid() && $this->nmo->settings->email_sending_method == 'default') {
if (Ninja::isHosted() && $this->company->account->isPaid() && $this->nmo->settings->email_sending_method == 'default') {
//check if outlook.
try {
@ -274,7 +286,7 @@ class NinjaMailerJob implements ShouldQueue
$domain = explode("@", $email)[1] ?? "";
$dns = dns_get_record($domain, DNS_MX);
$server = $dns[0]["target"];
if(stripos($server, "outlook.com") !== false) {
if (stripos($server, "outlook.com") !== false) {
$this->mailer = 'postmark';
$this->client_postmark_secret = config('services.postmark-outlook.token');
@ -286,12 +298,12 @@ class NinjaMailerJob implements ShouldQueue
}
$this->nmo
->mailable
->from(config('services.postmark-outlook.from.address'), $email_from_name);
->mailable
->from(config('services.postmark-outlook.from.address'), $email_from_name);
return $this;
}
} catch(\Exception $e) {
} catch (\Exception $e) {
nlog("problem switching outlook driver - hosted");
nlog($e->getMessage());
@ -324,7 +336,14 @@ class NinjaMailerJob implements ShouldQueue
$this->mailer = 'mailgun';
$this->setMailgunMailer();
return $this;
case 'client_brevo':
$this->mailer = 'brevo';
$this->setBrevoMailer();
return $this;
case 'smtp':
$this->mailer = 'smtp';
$this->configureSmtpMailer();
return $this;
default:
break;
}
@ -336,6 +355,48 @@ class NinjaMailerJob implements ShouldQueue
return $this;
}
private function configureSmtpMailer(): void
{
$company = $this->company;
$smtp_host = $company->smtp_host;
$smtp_port = $company->smtp_port;
$smtp_username = $company->smtp_username;
$smtp_password = $company->smtp_password;
$smtp_encryption = $company->smtp_encryption ?? 'tls';
$smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null;
$smtp_verify_peer = $company->smtp_verify_peer ?? true;
config([
'mail.mailers.smtp' => [
'transport' => 'smtp',
'host' => $smtp_host,
'port' => $smtp_port,
'username' => $smtp_username,
'password' => $smtp_password,
'encryption' => $smtp_encryption,
'local_domain' => $smtp_local_domain,
'verify_peer' => $smtp_verify_peer,
'timeout' => 30,
],
]);
if (property_exists($this->nmo->settings, 'email_from_name') && strlen($this->nmo->settings->email_from_name) > 1) {
$email_from_name = $this->nmo->settings->email_from_name;
} else {
$email_from_name = $this->company->present()->name();
}
$user = $this->resolveSendingUser();
$sending_email = (isset ($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email;
$this->nmo
->mailable
->from($sending_email, $email_from_name);
}
/**
* Allows configuration of multiple mailers
* per company for use by self hosted users
@ -355,8 +416,8 @@ class NinjaMailerJob implements ShouldQueue
if (env($this->company->id . '_MAIL_FROM_ADDRESS')) {
$this->nmo
->mailable
->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME')));
->mailable
->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME')));
}
}
}
@ -374,6 +435,8 @@ class NinjaMailerJob implements ShouldQueue
$this->client_mailgun_domain = false;
$this->client_brevo_secret = false;
//always dump the drivers to prevent reuse
app('mail.manager')->forgetMailers();
}
@ -423,8 +486,8 @@ class NinjaMailerJob implements ShouldQueue
}
$this->nmo
->mailable
->from(config('services.mailgun.from.address'), $email_from_name);
->mailable
->from(config('services.mailgun.from.address'), $email_from_name);
}
@ -444,12 +507,35 @@ class NinjaMailerJob implements ShouldQueue
$user = $this->resolveSendingUser();
$sending_email = (isset($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email;
$sending_user = (isset($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name();
$sending_email = (isset ($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email;
$sending_user = (isset ($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name();
$this->nmo
->mailable
->from($sending_email, $sending_user);
->mailable
->from($sending_email, $sending_user);
}
/**
* Configures Brevo using client supplied secret
* as the Mailer
*/
private function setBrevoMailer()
{
if (strlen($this->nmo->settings->brevo_secret) > 2) {
$this->client_brevo_secret = $this->nmo->settings->brevo_secret;
} else {
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
$user = $this->resolveSendingUser();
$sending_email = (isset ($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email;
$sending_user = (isset ($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name();
$this->nmo
->mailable
->from($sending_email, $sending_user);
}
/**
@ -467,12 +553,12 @@ class NinjaMailerJob implements ShouldQueue
$user = $this->resolveSendingUser();
$sending_email = (isset($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email;
$sending_user = (isset($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name();
$sending_email = (isset ($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email;
$sending_user = (isset ($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name();
$this->nmo
->mailable
->from($sending_email, $sending_user);
->mailable
->from($sending_email, $sending_user);
}
/**
@ -498,11 +584,11 @@ class NinjaMailerJob implements ShouldQueue
}
$this->nmo
->mailable
->from($user->email, $user->name())
->withSymfonyMessage(function ($message) use ($token) {
$message->getHeaders()->addTextHeader('gmailtoken', $token);
});
->mailable
->from($user->email, $user->name())
->withSymfonyMessage(function ($message) use ($token) {
$message->getHeaders()->addTextHeader('gmailtoken', $token);
});
}
/**
@ -526,7 +612,7 @@ class NinjaMailerJob implements ShouldQueue
}
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
} catch(\Exception $e) {
} catch (\Exception $e) {
$this->logMailError('Gmail Token Invalid', $this->company->clients()->first());
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
@ -546,7 +632,7 @@ class NinjaMailerJob implements ShouldQueue
* Now that our token is refreshed and valid we can boot the
* mail driver at runtime and also set the token which will persist
* just for this request.
*/
*/
$token = $user->oauth_user_token->access_token;
@ -557,11 +643,11 @@ class NinjaMailerJob implements ShouldQueue
}
$this->nmo
->mailable
->from($user->email, $user->name())
->withSymfonyMessage(function ($message) use ($token) {
$message->getHeaders()->addTextHeader('gmailtoken', $token);
});
->mailable
->from($user->email, $user->name())
->withSymfonyMessage(function ($message) use ($token) {
$message->getHeaders()->addTextHeader('gmailtoken', $token);
});
}
/**
@ -574,7 +660,7 @@ class NinjaMailerJob implements ShouldQueue
private function preFlightChecksFail(): bool
{
/* Always send regardless */
if($this->override) {
if ($this->override) {
return false;
}
@ -594,7 +680,7 @@ class NinjaMailerJob implements ShouldQueue
}
/* GMail users are uncapped */
if (Ninja::isHosted() && (in_array($this->nmo->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun']))) {
if (Ninja::isHosted() && (in_array($this->nmo->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun', 'client_brevo']))) {
return false;
}
@ -638,21 +724,23 @@ class NinjaMailerJob implements ShouldQueue
*/
private function logMailError($errors, $recipient_object): void
{
(new SystemLogger(
$errors,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_SEND,
SystemLog::TYPE_FAILURE,
$recipient_object,
$this->nmo->company
))->handle();
(
new SystemLogger(
$errors,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_SEND,
SystemLog::TYPE_FAILURE,
$recipient_object,
$this->nmo->company
)
)->handle();
$job_failure = new EmailFailure($this->nmo->company->company_key);
$job_failure->string_metric5 = 'failed_email';
$job_failure->string_metric6 = substr($errors, 0, 150);
LightLogs::create($job_failure)
->send();
->send();
$job_failure = null;
}
@ -677,8 +765,8 @@ class NinjaMailerJob implements ShouldQueue
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => config('ninja.o365.client_id') ,
'client_secret' => config('ninja.o365.client_secret') ,
'client_id' => config('ninja.o365.client_id'),
'client_secret' => config('ninja.o365.client_secret'),
'scope' => 'email Mail.Send offline_access profile User.Read openid',
'grant_type' => 'refresh_token',
'refresh_token' => $user->oauth_user_refresh_token

View File

@ -74,7 +74,7 @@ class BankTransactionSync implements ShouldQueue
if ($account->isEnterprisePaidClient()) {
$account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->where('disabled_upstream', 0)->cursor()->each(function ($bank_integration) use ($account) {
(new ProcessBankTransactionsYodlee($account->id, $bank_integration))->handle();
(new ProcessBankTransactionsYodlee($account->bank_integration_account_id, $bank_integration))->handle();
});
}
@ -90,7 +90,14 @@ class BankTransactionSync implements ShouldQueue
if ((Ninja::isSelfHost() || (Ninja::isHosted() && $account->isEnterprisePaidClient()))) {
$account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('disabled_upstream', 0)->cursor()->each(function ($bank_integration) {
(new ProcessBankTransactionsNordigen($bank_integration))->handle();
try {
(new ProcessBankTransactionsNordigen($bank_integration))->handle();
}
catch(\Exception $e) {
sleep(20);
}
sleep(5);
});
}

View File

@ -45,7 +45,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
private $entity;
private array $default_response = [
private array $default_response = [
'recipients' => '',
'subject' => 'Message not found.',
'entity' => '',
@ -53,6 +53,8 @@ class ProcessPostmarkWebhook implements ShouldQueue
'events' => [],
];
private ?Company $company = null;
/**
* Create a new job instance.
*
@ -64,11 +66,11 @@ class ProcessPostmarkWebhook implements ShouldQueue
private function getSystemLog(string $message_id): ?SystemLog
{
return SystemLog::query()
->where('company_id', $this->invitation->company_id)
->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE)
->whereJsonContains('log', ['MessageID' => $message_id])
->orderBy('id', 'desc')
->first();
->where('company_id', $this->invitation->company_id)
->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE)
->whereJsonContains('log', ['MessageID' => $message_id])
->orderBy('id', 'desc')
->first();
}
@ -87,12 +89,12 @@ class ProcessPostmarkWebhook implements ShouldQueue
public function handle()
{
MultiDB::findAndSetDbByCompanyKey($this->request['Tag']);
$company = Company::where('company_key', $this->request['Tag'])->first();
$this->company = Company::where('company_key', $this->request['Tag'])->first();
$this->invitation = $this->discoverInvitation($this->request['MessageID']);
if ($company && $this->request['RecordType'] == 'SpamComplaint' && config('ninja.notification.slack')) {
$company->notification(new EmailSpamNotification($company))->ninja();
if ($this->company && $this->request['RecordType'] == 'SpamComplaint' && config('ninja.notification.slack')) {
$this->company->notification(new EmailSpamNotification($this->company))->ninja();
}
if (!$this->invitation) {
@ -108,8 +110,8 @@ class ProcessPostmarkWebhook implements ShouldQueue
return $this->processDelivery();
case 'Bounce':
if($this->request['Subject'] == ctrans('texts.confirmation_subject')) {
$company->notification(new EmailBounceNotification($this->request['Email']))->ninja();
if ($this->request['Subject'] == ctrans('texts.confirmation_subject')) {
$this->company->notification(new EmailBounceNotification($this->request['Email']))->ninja();
}
return $this->processBounce();
@ -169,19 +171,21 @@ class ProcessPostmarkWebhook implements ShouldQueue
$sl = $this->getSystemLog($this->request['MessageID']);
if($sl) {
if ($sl) {
$this->updateSystemLog($sl, $data);
return;
}
(new SystemLogger(
$data,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_OPENED,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->invitation->contact->client,
$this->invitation->company
))->handle();
(
new SystemLogger(
$data,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_OPENED,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->invitation->contact->client,
$this->invitation->company
)
)->handle();
}
// {
@ -207,19 +211,21 @@ class ProcessPostmarkWebhook implements ShouldQueue
$sl = $this->getSystemLog($this->request['MessageID']);
if($sl) {
if ($sl) {
$this->updateSystemLog($sl, $data);
return;
}
(new SystemLogger(
$data,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_DELIVERY,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->invitation->contact->client,
$this->invitation->company
))->handle();
(
new SystemLogger(
$data,
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_DELIVERY,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->invitation->contact->client,
$this->invitation->company
)
)->handle();
}
// {
@ -265,7 +271,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
$sl = $this->getSystemLog($this->request['MessageID']);
if($sl) {
if ($sl) {
$this->updateSystemLog($sl, $data);
return;
}
@ -316,7 +322,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
$sl = $this->getSystemLog($this->request['MessageID']);
if($sl) {
if ($sl) {
$this->updateSystemLog($sl, $data);
}
@ -349,7 +355,9 @@ class ProcessPostmarkWebhook implements ShouldQueue
public function getRawMessage(string $message_id)
{
$postmark = new PostmarkClient(config('services.postmark.token'));
$postmark_secret = !empty($this->company->settings->postmark_secret) ? $this->company->settings->postmark_secret : config('services.postmark.token');
$postmark = new PostmarkClient($postmark_secret);
$messageDetail = $postmark->getOutboundMessageDetails($message_id);
return $messageDetail;
@ -362,7 +370,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
$messageDetail = $this->getRawMessage($message_id);
$event = collect($messageDetail->messageevents)->first(function ($event) {
$event = collect($messageDetail->messageevents)->first(function ($event) {
return $event?->Details?->BounceID ?? false;
@ -374,29 +382,31 @@ class ProcessPostmarkWebhook implements ShouldQueue
private function fetchMessage(): array
{
if(strlen($this->request['MessageID']) < 1) {
if (strlen($this->request['MessageID']) < 1) {
return $this->default_response;
}
try {
$postmark = new PostmarkClient(config('services.postmark.token'));
$postmark_secret = !empty($this->company->settings->postmark_secret) ? $this->company->settings->postmark_secret : config('services.postmark.token');
$postmark = new PostmarkClient($postmark_secret);
$messageDetail = $postmark->getOutboundMessageDetails($this->request['MessageID']);
$recipients = collect($messageDetail['recipients'])->flatten()->implode(',');
$subject = $messageDetail->subject ?? '';
$events = collect($messageDetail->messageevents)->map(function ($event) {
$events = collect($messageDetail->messageevents)->map(function ($event) {
return [
'bounce_id' => $event?->Details?->BounceID ?? '',
'recipient' => $event->Recipient ?? '',
'status' => $event->Type ?? '',
'delivery_message' => $event->Details->DeliveryMessage ?? $event->Details->Summary ?? '',
'server' => $event->Details->DestinationServer ?? '',
'server_ip' => $event->Details->DestinationIP ?? '',
'date' => \Carbon\Carbon::parse($event->ReceivedAt)->format('Y-m-d H:i:s') ?? '',
];
'bounce_id' => $event?->Details?->BounceID ?? '',
'recipient' => $event->Recipient ?? '',
'status' => $event->Type ?? '',
'delivery_message' => $event->Details->DeliveryMessage ?? $event->Details->Summary ?? '',
'server' => $event->Details->DestinationServer ?? '',
'server_ip' => $event->Details->DestinationIP ?? '',
'date' => \Carbon\Carbon::parse($event->ReceivedAt)->format('Y-m-d H:i:s') ?? '',
];
})->toArray();

View File

@ -76,12 +76,12 @@ class SendRecurring implements ShouldQueue
$invoice = $invoice->service()
->markSent()
->applyNumber()
->fillDefaults()
->fillDefaults(true)
->adjustInventory()
->save();
} else {
$invoice = $invoice->service()
->fillDefaults()
->fillDefaults(true)
->save();
}

View File

@ -123,7 +123,7 @@ class WebhookSingle implements ShouldQueue
]);
(new SystemLogger(
['message' => $response->getBody()->getHeaders(), 'body' => $data],
['message' => $response->getHeaders(), 'body' => $data],
SystemLog::CATEGORY_WEBHOOK,
SystemLog::EVENT_WEBHOOK_SUCCESS,
SystemLog::TYPE_WEBHOOK_RESPONSE,

View File

@ -21,6 +21,7 @@ use Illuminate\Support\Str;
use App\Models\CompanyToken;
use App\Models\ClientContact;
use App\Models\VendorContact;
use App\DataProviders\SMSNumbers;
use Illuminate\Support\Facades\DB;
/**
@ -537,6 +538,10 @@ class MultiDB
$current_db = config('database.default');
if(SMSNumbers::hasNumber($phone)){
return true;
}
foreach (self::$dbs as $db) {
self::setDB($db);
if ($exists = Account::where('account_sms_verification_number', $phone)->where('account_sms_verified', true)->exists()) {

View File

@ -12,15 +12,17 @@
namespace App\Livewire;
use App\Models\Client;
use App\Models\Invoice;
use Livewire\Component;
use App\Libraries\MultiDB;
use Illuminate\Support\Str;
use App\Models\ClientContact;
use App\Models\CompanyGateway;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash;
use Livewire\Attributes\Computed;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Livewire\Component;
class RequiredClientInfo extends Component
{
@ -31,10 +33,7 @@ class RequiredClientInfo extends Component
*/
public $show_terms = false;
/**
* @var array
*/
public $invoice;
public $invoice_terms;
/**
* @var bool
@ -49,18 +48,40 @@ class RequiredClientInfo extends Component
/**
* @var ClientContact
*/
public $contact;
public $contact_id;
/**
* @var \App\Models\Client
*/
public $client;
public $client_id;
/**
* @var array
*/
public $countries;
public $client_name;
public $contact_first_name;
public $contact_last_name;
public $contact_email;
public $client_phone;
public $client_address_line_1;
public $client_city;
public $client_state;
public $client_country_id;
public $client_postal_code;
public $client_shipping_address_line_1;
public $client_shipping_city;
public $client_shipping_state;
public $client_shipping_postal_code;
public $client_shipping_country_id;
public $client_custom_value1;
public $client_custom_value2;
public $client_custom_value3;
public $client_custom_value4;
/**
* Mappings for updating the database. Left side is mapping from gateway,
* right side is column in database.
@ -113,50 +134,97 @@ class RequiredClientInfo extends Component
];
protected $rules = [
'client.address1' => '',
'client.address2' => '',
'client.city' => '',
'client.state' => '',
'client.postal_code' => '',
'client.country_id' => '',
'client.shipping_address1' => '',
'client.shipping_address2' => '',
'client.shipping_city' => '',
'client.shipping_state' => '',
'client.shipping_postal_code' => '',
'client.shipping_country_id' => '',
'contact.first_name' => '',
'contact.last_name' => '',
'contact.email' => '',
'client.name' => '',
'client.website' => '',
'client.phone' => '',
'client.custom_value1' => '',
'client.custom_value2' => '',
'client.custom_value3' => '',
'client.custom_value4' => '',
// 'client.address1' => '',
// 'client.address2' => '',
// 'client.city' => '',
// 'client.state' => '',
// 'client.postal_code' => '',
// 'client.country_id' => '',
// 'client.shipping_address1' => '',
// 'client.shipping_address2' => '',
// 'client.shipping_city' => '',
// 'client.shipping_state' => '',
// 'client.shipping_postal_code' => '',
// 'client.shipping_country_id' => '',
// 'contact.first_name' => '',
// 'contact.last_name' => '',
// 'contact.email' => '',
// 'client.name' => '',
// 'client.website' => '',
// 'client.phone' => '',
// 'client.custom_value1' => '',
// 'client.custom_value2' => '',
// 'client.custom_value3' => '',
// 'client.custom_value4' => '',
'client_name' => '',
'client_website' => '',
'client_phone' => '',
'client_address_line_1' => '',
'client_address_line_2' => '',
'client_city' => '',
'client_state' => '',
'client_postal_code' => '',
'client_country_id' => '',
'client_shipping_address_line_1' => '',
'client_shipping_address_line_2' => '',
'client_shipping_city' => '',
'client_shipping_state' => '',
'client_shipping_postal_code' => '',
'client_shipping_country_id' => '',
'client_custom_value1' => '',
'client_custom_value2' => '',
'client_custom_value3' => '',
'client_custom_value4' => '',
'contact_first_name' => '',
'contact_last_name' => '',
'contact_email' => '',
];
public $show_form = false;
public $company;
public $company_id;
public $company_gateway_id;
public $db;
public function mount()
{
MultiDB::setDb($this->company->db);
MultiDB::setDb($this->db);
$contact = ClientContact::withTrashed()->find($this->contact_id);
$company = $contact->company;
$this->client = $this->contact->client;
$this->client_name = $contact->client->name;
$this->contact_first_name = $contact->first_name;
$this->contact_last_name = $contact->last_name;
$this->contact_email = $contact->email;
$this->client_phone = $contact->client->phone;
$this->client_address_line_1 = $contact->client->address1;
$this->client_city = $contact->client->city ;
$this->client_state = $contact->client->state;
$this->client_country_id = $contact->client->country_id;
$this->client_postal_code = $contact->client->postal_code;
$this->client_shipping_address_line_1 = $contact->client->shipping_address1;
$this->client_shipping_city = $contact->client->shipping_city;
$this->client_shipping_state = $contact->client->shipping_state;
$this->client_shipping_postal_code = $contact->client->shipping_postal_code;
$this->client_shipping_country_id = $contact->client->shipping_country_id;
$this->client_custom_value1 = $contact->client->custom_value1;
$this->client_custom_value2 = $contact->client->custom_value2;
$this->client_custom_value3 = $contact->client->custom_value3;
$this->client_custom_value4 = $contact->client->custom_value4;
if ($this->company->settings->show_accept_invoice_terms && request()->query('hash')) {
// $this->client = $this->contact->client;
if ($company->settings->show_accept_invoice_terms && request()->query('hash')) {
$this->show_terms = true;
$this->terms_accepted = false;
$this->show_form = true;
$hash = Cache::get(request()->input('hash'));
$invoice = Invoice::find($this->decodePrimaryKey($hash['invoice_id']));
$this->invoice = Invoice::find($this->decodePrimaryKey($hash['invoice_id']));
$this->invoice_terms = $invoice->terms;
}
count($this->fields) > 0 || $this->show_terms
@ -164,6 +232,24 @@ class RequiredClientInfo extends Component
: $this->show_form = false;
}
#[Computed]
public function contact()
{
MultiDB::setDb($this->db);
return ClientContact::withTrashed()->find($this->contact_id);
}
#[Computed]
public function client()
{
MultiDB::setDb($this->db);
return ClientContact::withTrashed()->find($this->contact_id)->client;
}
public function toggleTermsAccepted()
{
$this->terms_accepted = !$this->terms_accepted;
@ -171,6 +257,10 @@ class RequiredClientInfo extends Component
public function handleSubmit(array $data): bool
{
MultiDB::setDb($this->db);
$contact = ClientContact::withTrashed()->find($this->contact_id);
$rules = [];
collect($this->fields)->map(function ($field) use (&$rules) {
@ -192,7 +282,7 @@ class RequiredClientInfo extends Component
if ($this->updateClientDetails($data)) {
$this->dispatch(
'passed-required-fields-check',
client_postal_code: $this->contact->client->postal_code
client_postal_code: $contact->client->postal_code
);
//if stripe is enabled, we want to update the customer at this point.
@ -209,6 +299,11 @@ class RequiredClientInfo extends Component
$client = [];
$contact = [];
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
foreach ($data as $field => $value) {
if (Str::startsWith($field, 'client_')) {
$client[$this->mappings[$field]] = $value;
@ -219,20 +314,43 @@ class RequiredClientInfo extends Component
}
}
$contact_update = $this->contact
$_contact->first_name = $this->contact_first_name;
$_contact->last_name = $this->contact_last_name;
$_contact->client->name = $this->client_name;
$_contact->email = $this->contact_email;
$_contact->client->phone = $this->client_phone;
$_contact->client->address1 = $this->client_address_line_1;
$_contact->client->city = $this->client_city;
$_contact->client->state = $this->client_state;
$_contact->client->country_id = $this->client_country_id;
$_contact->client->postal_code = $this->client_postal_code;
$_contact->client->shipping_address1 = $this->client_shipping_address_line_1;
$_contact->client->shipping_city = $this->client_shipping_city;
$_contact->client->shipping_state = $this->client_shipping_state;
$_contact->client->shipping_postal_code = $this->client_shipping_postal_code;
$_contact->client->shipping_country_id = $this->client_shipping_country_id;
$_contact->client->custom_value1 = $this->client_custom_value1;
$_contact->client->custom_value2 = $this->client_custom_value2;
$_contact->client->custom_value3 = $this->client_custom_value3;
$_contact->client->custom_value4 = $this->client_custom_value4;
$_contact->push();
$contact_update = $_contact
->fill($contact)
->push();
$client_update = $this->contact->client
$client_update = $_contact->client
->fill($client)
->push();
if ($contact_update && $client_update) {
if ($_contact) {
/** @var \App\Models\CompanyGateway $cg */
$cg = CompanyGateway::find($this->company_gateway_id);
if ($cg && $cg->update_details) {
$payment_gateway = $cg->driver($this->client)->init();
$payment_gateway = $cg->driver($_contact->client)->init();
if (method_exists($payment_gateway, "updateCustomer")) {
$payment_gateway->updateCustomer();
@ -247,11 +365,15 @@ class RequiredClientInfo extends Component
public function checkFields()
{
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
foreach ($this->fields as $index => $field) {
$_field = $this->mappings[$field['name']];
if (Str::startsWith($field['name'], 'client_')) {
if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field}) || in_array($_field, $this->client_address_array)) {
if (empty($_contact->client->{$_field}) || is_null($_contact->client->{$_field}) || in_array($_field, $this->client_address_array)) {
$this->show_form = true;
} else {
$this->fields[$index]['filled'] = true;
@ -259,7 +381,7 @@ class RequiredClientInfo extends Component
}
if (Str::startsWith($field['name'], 'contact_')) {
if (empty($this->contact->{$_field}) || is_null($this->contact->{$_field}) || str_contains($this->contact->{$_field}, '@example.com')) {
if (empty($_contact->{$_field}) || is_null($_contact->{$_field}) || str_contains($_contact->{$_field}, '@example.com')) {
$this->show_form = true;
} else {
$this->fields[$index]['filled'] = true;
@ -289,14 +411,18 @@ class RequiredClientInfo extends Component
public function handleCopyBilling(): void
{
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);
$this->dispatch(
'update-shipping-data',
client_shipping_address_line_1: $this->contact->client->address1,
client_shipping_address_line_2: $this->contact->client->address2,
client_shipping_city: $this->contact->client->city,
client_shipping_state: $this->contact->client->state,
client_shipping_postal_code: $this->contact->client->postal_code,
client_shipping_country_id: $this->contact->client->country_id,
client_shipping_address_line_1: $_contact->client->address1,
client_shipping_address_line_2: $_contact->client->address2,
client_shipping_city: $_contact->client->city,
client_shipping_state: $_contact->client->state,
client_shipping_postal_code: $_contact->client->postal_code,
client_shipping_country_id: $_contact->client->country_id,
);
}

View File

@ -52,7 +52,7 @@ class ClientStatement extends Mailable
public function content()
{
return new Content(
view: $this->data['company']->account->isPremium() ? 'email.template.client_premium' : 'email.template.client',
view: 'email.template.client',
text: 'email.template.text',
with: [
'text_body' => $this->data['body'],

View File

@ -75,7 +75,8 @@ class TemplateEmail extends Mailable
$template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
$template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client';
// $template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client';
$template_name = 'email.template.client';
}
if ($this->build_email->getTemplate() == 'custom') {

View File

@ -72,7 +72,8 @@ class VendorTemplateEmail extends Mailable
$template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
$template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client';
// $template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client';
$template_name = 'email.template.client';
}
if ($this->build_email->getTemplate() == 'custom') {

View File

@ -102,7 +102,7 @@ class Account extends BaseModel
private $free_plan_email_quota = 20;
private $paid_plan_email_quota = 400;
private $paid_plan_email_quota = 300;
/**
* @var string
@ -494,7 +494,7 @@ class Account extends BaseModel
return 0;
}
if (Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0) {
if (Carbon::createFromTimestamp($this->created_at)->diffInWeeks() <= 1) {
return 20;
}
@ -503,11 +503,13 @@ class Account extends BaseModel
}
if ($this->isPaid()) {
$multiplier = $this->plan == 'enterprise' ? 2 : 1.2;
$limit = $this->paid_plan_email_quota;
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 50;
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * (20 * $multiplier);
} else {
$limit = $this->free_plan_email_quota;
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 2;
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 1.5;
}
return min($limit, 1000);

View File

@ -485,7 +485,7 @@ class Client extends BaseModel implements HasLocalePreference
}
/*Company Settings*/
elseif ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) {
elseif ((property_exists($this->company->settings, $setting) !== false) && (isset($this->company->settings->{$setting}) !== false)) {
return $this->company->settings->{$setting};
} elseif (property_exists(CompanySettings::defaults(), $setting)) {
return CompanySettings::defaults()->{$setting};

View File

@ -132,6 +132,7 @@ class CompanyGateway extends BaseModel
// const TYPE_EWAY = 313;
// const TYPE_FORTE = 314;
// const PAYPAL_PPCP = 323;
// const SQUARE = 320;
public $gateway_consts = [
'38f2c48af60c7dd69e04248cbb24c36e' => 300,
@ -144,7 +145,7 @@ class CompanyGateway extends BaseModel
'8fdeed552015b3c7b44ed6c8ebd9e992' => 309,
'd6814fc83f45d2935e7777071e629ef9' => 310,
'bbd736b3254b0aabed6ad7fda1298c88' => 311,
'1bd651fb213ca0c9d66ae3c336dc77e7' => 312,
'1bd651fb213ca0c9d66ae3c336dc77e8' => 312,
'944c20175bbe6b9972c05bcfe294c2c7' => 313,
'kivcvjexxvdiyqtj3mju5d6yhpeht2xs' => 314,
'65faab2ab6e3223dbe848b1686490baz' => 320,

View File

@ -193,6 +193,18 @@ class AuthorizePaymentDriver extends BaseDriver
public function import()
{
$this->init();
return (new AuthorizeCustomer($this))->importCustomers();
}
public function importCustomers()
{
return $this->import();
}
public function auth(): bool
{
return $this->init()->getPublicClientKey() ?? false;
}
}

View File

@ -806,4 +806,14 @@ class BaseDriver extends AbstractPaymentDriver
{
return true;
}
public function auth(): bool
{
return true;
}
public function importCustomers()
{
}
}

View File

@ -12,29 +12,40 @@
namespace App\PaymentDrivers;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use Exception;
use App\Models\Client;
use Braintree\Gateway;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\SystemLog;
use App\Models\GatewayType;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\Models\ClientContact;
use App\Factory\ClientFactory;
use Illuminate\Support\Carbon;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Factory\ClientContactFactory;
use App\PaymentDrivers\Braintree\ACH;
use App\PaymentDrivers\Braintree\CreditCard;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Database\QueryException;
use App\PaymentDrivers\Braintree\PayPal;
use Braintree\Gateway;
use Exception;
use Illuminate\Support\Facades\Validator;
use App\PaymentDrivers\Braintree\CreditCard;
class BraintreePaymentDriver extends BaseDriver
{
use GeneratesCounter;
public $refundable = true;
public $token_billing = true;
public $can_authorise_credit_card = true;
private bool $completed = true;
/**
* @var Gateway;
*/
@ -157,34 +168,6 @@ class BraintreePaymentDriver extends BaseDriver
}
}
// public function updateCustomer()
// {
// $customer = $this->findOrCreateCustomer();
// $result = $this->gateway->customer()->update(
// $customer->id,
// [
// 'firstName' => $this->client->present()->name(),
// 'email' => $this->client->present()->email(),
// 'phone' => $this->client->present()->phone(),
// 'creditCard' => [
// 'billingAddress' => [
// 'options' => [
// 'updateExisting' => true
// ],
// 'firstName' => $this->client->present()->first_name() ?: $this->client->present()->name(),
// 'lastName' => $this->client->present()->last_name() ?: '',
// 'streetAddress' => $this->client->address1 ?: '',
// 'extendedAddress' =>$this->client->address2 ?: '',
// 'locality' => $this->client->city ?: '',
// 'postalCode' => $this->client->postal_code ?: '',
// 'countryCodeAlpha2' => $this->client->country ? $this->client->country->iso_3166_2 : 'US',
// ],
// ],
// ]
// );
// }
public function refund(Payment $payment, $amount, $return_client_response = false)
{
$this->init();
@ -324,13 +307,198 @@ class BraintreePaymentDriver extends BaseDriver
nlog('braintree webhook');
// if($webhookNotification)
// nlog($webhookNotification->kind);
// // Example values for webhook notification properties
// $message = $webhookNotification->kind; // "subscription_went_past_due"
// $message = $webhookNotification->timestamp->format('D M j G:i:s T Y'); // "Sun Jan 1 00:00:00 UTC 2012"
return response()->json([], 200);
}
public function auth(): bool
{
try {
$ct =$this->init()->gateway->clientToken()->generate();
return true;
}
catch(\Exception $e) {
}
return false;
}
private function find(string $customer_id = '') {
try {
return $this->init()->gateway->customer()->find($customer_id);
}
catch(\Exception $e){
return false;
}
return false;
}
private function findTokens(string $gateway_customer_reference)
{
return ClientGatewayToken::where('company_id', $this->company_gateway->company_id)
->where('gateway_customer_reference', $gateway_customer_reference)
->exists();
}
private function getToken(string $token, string $gateway_customer_reference)
{
return ClientGatewayToken::where('company_id', $this->company_gateway->company_id)
->where('gateway_customer_reference', $gateway_customer_reference)
->where('token', $token)
->first();
}
private function findClient(string $email) {
return ClientContact::where('company_id', $this->company_gateway->company_id)
->where('email', $email)
->first()->client ?? false;
}
private function addClientCards(Client $client, array $cards)
{
$this->client = $client;
foreach($cards as $card) {
if($this->getToken($card->token, $card->customerId) || Carbon::createFromDate($card->expirationYear, $card->expirationMonth, '1')->lt(now()))
continue;
$payment_meta = new \stdClass();
$payment_meta->exp_month = (string) $card->expirationMonth;
$payment_meta->exp_year = (string) $card->expirationYear;
$payment_meta->brand = (string) $card->cardType;
$payment_meta->last4 = (string) $card->last4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$data = [
'payment_meta' => $payment_meta,
'token' => $card->token,
'payment_method_id' => GatewayType::CREDIT_CARD,
];
$this->storeGatewayToken($data, ['gateway_customer_reference' => $card->customerId]);
nlog("adding card to customer payment profile");
}
}
public function createNinjaClient(mixed $customer): Client
{
$client = ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id);
$b_business_address = count($customer->addresses) >= 1 ? $customer->addresses[0] : false;
$b_shipping_address = count($customer->addresses) > 1 ? $customer->addresses[1] : false;
$import_client_data = [];
if($b_business_address) {
$braintree_address =
[
'address1' => $b_business_address->extendedAddress ?? '',
'address2' => $b_business_address->streetAddress ?? '',
'city' => $b_business_address->locality ?? '',
'postal_code' => $b_business_address->postalCode ?? '',
'state' => $b_business_address->region ?? '',
'country_id' => $b_business_address->countryCodeNumeric ?? '840',
];
$import_client_data = array_merge($import_client_data, $braintree_address);
}
if($b_shipping_address) {
$braintree_shipping_address =
[
'shipping_address1' => $b_shipping_address->extendedAddress ?? '',
'shipping_address2' => $b_shipping_address->streetAddress ?? '',
'shipping_city' => $b_shipping_address->locality ?? '',
'shipping_postal_code' => $b_shipping_address->postalCode ?? '',
'shipping_state' => $b_shipping_address->region ?? '',
'shipping_country_id' => $b_shipping_address->countryCodeNumeric ?? '840',
];
$import_client_data = array_merge($import_client_data, $braintree_shipping_address);
}
$client->fill($import_client_data);
$client->phone = $customer->phone ?? '';
$client->name = $customer->company ?? $customer->firstName;
$settings = $client->settings;
$settings->currency_id = (string) $this->company_gateway->company->settings->currency_id;
$client->settings = $settings;
$client->save();
$contact = ClientContactFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id);
$contact->first_name = $customer->firstName ?? '';
$contact->last_name = $customer->lastName ?? '';
$contact->email = $customer->email ?? '';
$contact->phone = $customer->phone ?? '';
$contact->client_id = $client->id;
$contact->saveQuietly();
if (! isset($client->number) || empty($client->number)) {
$x = 1;
do {
try {
$client->number = $this->getNextClientNumber($client);
$client->saveQuietly();
$this->completed = false;
} catch (QueryException $e) {
$x++;
if ($x > 10) {
$this->completed = false;
}
}
} while ($this->completed);
} else {
$client->saveQuietly();
}
return $client;
}
public function importCustomers()
{
$customers = $this->init()->gateway->customer()->all();
foreach($customers as $c){
$customer = $this->find($c->id);
// nlog(count($customer->creditCards). " Exists for {$c->id}");
if(!$customer)
continue;
$client = $this->findClient($customer->email);
if(!$this->findTokens($c->id) && !$client) {
//customer is not referenced in the system - create client
$client = $this->createNinjaClient($customer);
// nlog("Creating new Client");
}
$this->addClientCards($client, $customer->creditCards);
// nlog("Adding Braintree Client: {$c->id} => {$client->id}");
}
}
}

View File

@ -12,37 +12,38 @@
namespace App\PaymentDrivers;
use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use Exception;
use App\Models\Company;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\SystemLog;
use Checkout\CheckoutSdk;
use Checkout\Environment;
use Checkout\Common\Phone;
use App\Models\GatewayType;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\CheckoutCom\CheckoutWebhook;
use App\PaymentDrivers\CheckoutCom\CreditCard;
use App\PaymentDrivers\CheckoutCom\Utilities;
use App\Utils\Traits\SystemLogTrait;
use Illuminate\Support\Carbon;
use App\Jobs\Util\SystemLogger;
use App\Exceptions\PaymentFailed;
use App\Models\ClientGatewayToken;
use Checkout\CheckoutApiException;
use App\Utils\Traits\SystemLogTrait;
use Checkout\Payments\RefundRequest;
use Illuminate\Support\Facades\Auth;
use Checkout\CheckoutArgumentException;
use Checkout\CheckoutAuthorizationException;
use Checkout\CheckoutSdk;
use Checkout\Common\Phone;
use Checkout\Customers\CustomerRequest;
use Checkout\Environment;
use Checkout\CheckoutAuthorizationException;
use App\PaymentDrivers\CheckoutCom\Utilities;
use Checkout\Payments\Request\PaymentRequest;
use App\PaymentDrivers\CheckoutCom\CreditCard;
use App\PaymentDrivers\CheckoutCom\CheckoutWebhook;
use App\Http\Requests\Payments\PaymentWebhookRequest;
use Checkout\Payments\Request\Source\RequestIdSource;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use Checkout\Payments\Previous\PaymentRequest as PreviousPaymentRequest;
use Checkout\Payments\Previous\Source\RequestIdSource as SourceRequestIdSource;
use Checkout\Payments\RefundRequest;
use Checkout\Payments\Request\PaymentRequest;
use Checkout\Payments\Request\Source\RequestIdSource;
use Exception;
use Illuminate\Support\Facades\Auth;
class CheckoutComPaymentDriver extends BaseDriver
{
@ -534,4 +535,87 @@ class CheckoutComPaymentDriver extends BaseDriver
{
// Gateway doesn't support this feature.
}
public function auth(): bool
{
try{
$this->init()->gateway->getCustomersClient('x');
return true;
}
catch(\Exception $e){
}
return false;
}
private function getToken(string $token, $gateway_customer_reference)
{
return ClientGatewayToken::query()
->where('company_id', $this->company_gateway->company_id)
->where('gateway_customer_reference', $gateway_customer_reference)
->where('token', $token)
->first();
}
/**
* ImportCustomers
*
* Only their methods because checkout.com
* does not have a list route for customers
*
* @return void
*/
public function importCustomers()
{
$this->init();
$this->company_gateway
->company
->clients()
->cursor()
->each(function ($client){
if(!str_contains($client->present()->email(), "@"))
return;
try{
$customer = $this->gateway->getCustomersClient()->get($client->present()->email());
}
catch(\Exception $e) {
nlog("Checkout: Customer not found");
return;
}
$this->client = $client;
nlog($customer['instruments']);
foreach($customer['instruments'] as $card)
{
if(
$card['type'] != 'card' ||
Carbon::createFromDate($card['expiry_year'], $card['expiry_month'], '1')->lt(now()) ||
$this->getToken($card['id'], $customer['id'])
)
continue;
$payment_meta = new \stdClass();
$payment_meta->exp_month = (string) $card['expiry_month'];
$payment_meta->exp_year = (string) $card['expiry_year'];
$payment_meta->brand = (string) $card['scheme'];
$payment_meta->last4 = (string) $card['last4'];
$payment_meta->type = (int) GatewayType::CREDIT_CARD;
$data = [
'payment_meta' => $payment_meta,
'token' => $card['id'],
'payment_method_id' => GatewayType::CREDIT_CARD,
];
$this->storeGatewayToken($data, ['gateway_customer_reference' => $customer['id']]);
}
});
}
}

View File

@ -211,4 +211,24 @@ class EwayPaymentDriver extends BaseDriver
return $fields;
}
public function auth(): bool
{
$response =$this->init()->eway->queryTransaction('xx');
return (bool) count($response->getErrors()) == 0;
}
/**
* importCustomers
*
* No support
* @return void
*/
public function importCustomers()
{
return true;
}
}

View File

@ -0,0 +1,137 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\Factory;
use App\Models\Client;
use App\Models\Company;
class ForteCustomerFactory
{
public function convertToNinja(array $customer, Company $company): array
{
return
collect([
'name' => $customer['company_name'] ?? $customer['first_name'],
'contacts' => [
[
'first_name' => $customer['first_name'],
'last_name' => $customer['last_name'],
'email' => $this->getBillingAddress($customer)['email'],
'phone' => $this->getBillingAddress($customer)['phone'],
]
],
'settings' => [
'currency_id' => $company->settings->currency_id,
],
])->merge($this->getShippingAddress($customer))
->merge($this->getBillingAddress($customer))
->toArray();
}
// public function convertToGateway(Client $client): array
// {
// }
private function getBillingAddress(array $customer): array
{
if(isset($customer['default_billing_address_token'])) {
foreach($customer['addresses'] as $address) {
if($address['address_token'] != $customer['default_billing_address_token'])
continue;
return [
'address1' => $address['physical_address']['street_line1'],
'address2' => $address['physical_address']['street_line2'],
'city' => $address['physical_address']['locality'],
'state' => $address['physical_address']['region'],
'postal_code' => $address['physical_address']['postal_code'],
'country_id' => '840',
'email' => $address['email'],
'phone' => $address['phone'],
];
}
}
if(isset($customer['addresses'][0])) {
$address = $customer['addresses'][0];
return [
'address1' => $address['physical_address']['street_line1'],
'address2' => $address['physical_address']['street_line2'],
'city' => $address['physical_address']['locality'],
'state' => $address['physical_address']['region'],
'postal_code' => $address['physical_address']['postal_code'],
'email' => $address['email'],
'phone' => $address['phone'],
'country_id' => '840',
];
}
return ['email' => '', 'phone' => ''];
}
private function getShippingAddress(array $customer): array
{
if(isset($customer['default_shipping_address_token'])) {
foreach($customer['addresses'] as $address) {
if($address['address_token'] != $customer['default_shipping_address_token']) {
continue;
}
return [
'shipping_address1' => $address['physical_address']['street_line1'],
'shipping_address2' => $address['physical_address']['street_line2'],
'shipping_city' => $address['physical_address']['locality'],
'shipping_state' => $address['physical_address']['region'],
'shipping_postal_code' => $address['physical_address']['postal_code'],
'shipping_country_id' => '840',
];
}
}
if(isset($customer['addresses'][1])){
$address = $customer['addresses'][1];
return [
'shipping_address1' => $address['physical_address']['street_line1'],
'shipping_address2' => $address['physical_address']['street_line2'],
'shipping_city' => $address['physical_address']['locality'],
'shipping_state' => $address['physical_address']['region'],
'shipping_postal_code' => $address['physical_address']['postal_code'],
'shipping_country_id' => '840',
'email' => $address['email'],
'phone' => $address['phone'],
];
}
return ['email' => '', 'phone' => ''];
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\Factory;
use App\Models\Company;
class PaytraceCustomerFactory
{
public function convertToNinja($customer, Company $company): array
{
return
collect([
'name' => $customer->billing_address->name ?? $customer->shipping_address->name,
'contacts' => [
[
'first_name' => $customer->billing_address->name ?? $customer->shipping_address->name,
'last_name' => '',
'email' => $customer->email,
'phone' => $customer->phone,
]
],
'currency_id' => $company->settings->currency_id,
'address1' => $customer->billing_address->street_address,
'address2' => $customer->billing_address->street_address2,
'city' => $customer->billing_address->city,
'state' => $customer->billing_address->state,
'postal_code' => $customer->billing_address->zip,
'country_id' => '840',
'shipping_address1' => $customer->shipping_address->street_address,
'shipping_address2' => $customer->shipping_address->street_address2,
'shipping_city' => $customer->shipping_address->city,
'shipping_state' => $customer->shipping_address->state,
'shipping_postal_code' => $customer->shipping_address->zip,
'shipping_country_id' => '840',
'settings' => [
'currency_id' => $company->settings->currency_id,
],
'card' => [
'token' => $customer->customer_id,
'last4' => $customer->credit_card->masked_number,
'expiry_month' => $customer->credit_card->expiration_month,
'expiry_year' => $customer->credit_card->expiration_year,
],
])
->toArray();
}
}

View File

@ -0,0 +1,132 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\Factory;
use App\Models\Company;
use App\Models\GatewayType;
class SquareCustomerFactory
{
/*
{
"id": "A537H7KAQWSAF8M8EM1Y23E16M",
"created_at": "2021-10-28T20:19:07.692Z",
"updated_at": "2024-01-09T20:14:21Z",
"cards": [
{
"id": "ccof:oG9wEmGAvoAnrBGt3GB",
"card_brand": "VISA",
"last_4": "5858",
"exp_month": 10,
"exp_year": 2023,
"cardholder_name": "Amelia Earhart",
"billing_address": {
"address_line_1": "500 Electric Ave",
"locality": "New York",
"administrative_district_level_1": "NY",
"postal_code": "94103",
"country": "US"
}
},
{
"id": "gftc:06c30c2b9772458a9e87b2880ee2ce1a",
"card_brand": "SQUARE_GIFT_CARD",
"last_4": "0895",
"exp_month": 12,
"exp_year": 2050,
"billing_address": {
"postal_code": "94103"
}
}
],
"given_name": "Amelia",
"family_name": "Earhart",
"email_address": "Amelia.Earhart@example.com",
"address": {
"address_line_1": "123 Main St",
"locality": "Seattle",
"administrative_district_level_1": "WA",
"postal_code": "98121",
"country": "US"
},
"phone_number": "1-212-555-4240",
"note": "a customer on seller account",
"reference_id": "YOUR_REFERENCE_ID",
"company_name": "ACME",
"preferences": {
"email_unsubscribed": false
},
"creation_source": "THIRD_PARTY",
"segment_ids": [
"8QJTJCE6AZSN6.REACHABLE",
"8QJTJCE6AZSN6.CARDS_ON_FILE",
"gv2:8H24YRM74H2030XWJWP9F0MAEW",
"gv2:4TR2NFVP8N63D9K1FZ5E62VD78"
],
"version": 4
},
*/
public function convertToNinja($customer, Company $company): array
{
$cards = [];
foreach($customer->getCards() ?? [] as $card){
$meta = new \stdClass;
$meta->exp_month = $card->getExpMonth();
$meta->exp_year = $card->getExpYear();
$meta->last4 = $card->getLast4();
$meta->brand = $card->getCardBrand();
$meta->type = GatewayType::CREDIT_CARD;
$cards[] = [
'token' => $card->getId(),
'payment_meta' => $meta,
'payment_method_id' => GatewayType::CREDIT_CARD,
'gateway_customer_reference' => $customer->getId(),
];
}
$address = $customer->getAddress();
return
collect([
'name' => $customer->getCompanyName() ?? ($customer->getGivenName() ?? '' ." " . $customer->getFamilyName() ?? ''),
'contacts' => [
[
'first_name' => $customer->getGivenName(),
'last_name' => $customer->getFamilyName(),
'email' => $customer->getEmailAddress(),
'phone' => $customer->getPhoneNumber(),
]
],
'currency_id' => $company->settings->currency_id,
'address1' => $address->getAddressLine1(),
'address2' => $address->getAddressLine2(),
'city' => $address->getLocality(),
'state' => $address->getAdministrativeDistrictLevel1(),
'postal_code' => $address->getPostalCode(),
'country_id' => '840',
'settings' => [
'currency_id' => $company->settings->currency_id,
],
'cards' => $cards,
])
->toArray();
}
}

View File

@ -11,13 +11,19 @@
namespace App\PaymentDrivers;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\SystemLog;
use App\PaymentDrivers\Forte\ACH;
use App\PaymentDrivers\Forte\CreditCard;
use App\Models\GatewayType;
use App\Models\ClientContact;
use App\Factory\ClientFactory;
use App\Jobs\Util\SystemLogger;
use App\Utils\Traits\MakesHash;
use App\PaymentDrivers\Forte\ACH;
use Illuminate\Support\Facades\Http;
use App\Repositories\ClientRepository;
use App\PaymentDrivers\Forte\CreditCard;
use App\Repositories\ClientContactRepository;
use App\PaymentDrivers\Factory\ForteCustomerFactory;
class FortePaymentDriver extends BaseDriver
{
@ -183,8 +189,140 @@ class FortePaymentDriver extends BaseDriver
];
}
// public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
// {
// return $this->payment_method->yourTokenBillingImplmentation();
// }
////////////////////////////////////////////
// DB
///////////////////////////////////////////
public function auth(): bool
{
$forte_base_uri = "https://sandbox.forte.net/api/v3/";
if ($this->company_gateway->getConfigField('testMode') == false) {
$forte_base_uri = "https://api.forte.net/v3/";
}
$forte_api_access_id = $this->company_gateway->getConfigField('apiAccessId');
$forte_secure_key = $this->company_gateway->getConfigField('secureKey');
$forte_auth_organization_id = $this->company_gateway->getConfigField('authOrganizationId');
$forte_organization_id = $this->company_gateway->getConfigField('organizationId');
$forte_location_id = $this->company_gateway->getConfigField('locationId');
$response = Http::withBasicAuth($forte_api_access_id, $forte_secure_key)
->withHeaders(['X-Forte-Auth-Organization-Id' => $forte_organization_id])
->get("{$forte_base_uri}/organizations/{$forte_organization_id}/locations/{$forte_location_id}/customers/");
return $response->successful();
}
public function baseUri(): string
{
$forte_base_uri = "https://sandbox.forte.net/api/v3/";
if ($this->company_gateway->getConfigField('testMode') == false) {
$forte_base_uri = "https://api.forte.net/v3/";
}
return $forte_base_uri;
}
private function getOrganisationId(): string
{
return $this->company_gateway->getConfigField('organizationId');
}
public function getLocationId(): string
{
return $this->company_gateway->getConfigField('locationId');
}
public function stubRequest()
{
$forte_api_access_id = $this->company_gateway->getConfigField('apiAccessId');
$forte_secure_key = $this->company_gateway->getConfigField('secureKey');
$forte_auth_organization_id = $this->company_gateway->getConfigField('authOrganizationId');
return Http::withBasicAuth($forte_api_access_id, $forte_secure_key)
->withHeaders(['X-Forte-Auth-Organization-Id' => $this->getOrganisationId()]);
}
private function getClient(?string $email)
{
return ClientContact::query()
->where('company_id', $this->company_gateway->company_id)
->where('email', $email)
->first();
}
public function getLocation()
{
$response = $this->stubRequest()
->withQueryParameters(['page_size' => 10000])
->get("{$this->baseUri()}/organizations/{$this->getOrganisationId()}/locations/{$this->getLocationId()}");
if($response->successful())
return $response->json();
return false;
}
public function updateFees()
{
$response = $this->getLocation();
if($response)
{
$body = $response['services'];
$fees_and_limits = $this->company_gateway->fees_and_limits;
if($body['card']['service_fee_percentage'] > 0 || $body['card']['service_fee_additional_amount'] > 0){
$fees_and_limits->{1}->fee_amount = $body['card']['service_fee_additional_amount'];
$fees_and_limits->{1}->fee_percent = $body['card']['service_fee_percentage'];
}
if($body['debit']['service_fee_percentage'] > 0 || $body['debit']['service_fee_additional_amount'] > 0) {
$fees_and_limits->{2}->fee_amount = $body['debit']['service_fee_additional_amount'];
$fees_and_limits->{2}->fee_percent = $body['debit']['service_fee_percentage'];
}
$this->company_gateway->fees_and_limits = $fees_and_limits;
$this->company_gateway->save();
}
return false;
}
public function importCustomers()
{
$response = $this->stubRequest()
->withQueryParameters(['page_size' => 10000])
->get("{$this->baseUri()}/organizations/{$this->getOrganisationId()}/locations/{$this->getLocationId()}/customers");
if($response->successful()){
foreach($response->json()['results'] as $customer)
{
$client_repo = new ClientRepository(new ClientContactRepository());
$factory = new ForteCustomerFactory();
$data = $factory->convertToNinja($customer, $this->company_gateway->company);
if(strlen($data['email']) == 0 || $this->getClient($data['email']))
continue;
$client_repo->save($data, ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id));
//persist any payment methods here!
}
}
}
}

View File

@ -84,11 +84,11 @@ class GoCardlessPaymentDriver extends BaseDriver
$types[] = GatewayType::DIRECT_DEBIT;
}
if (in_array($this->client->currency()->code, ['EUR', 'GBP'])) {
if ($this->client && in_array($this->client->currency()->code, ['EUR', 'GBP'])) {
$types[] = GatewayType::SEPA;
}
if ($this->client->currency()->code === 'GBP') {
if ($this->client && $this->client->currency()->code === 'GBP') {
$types[] = GatewayType::INSTANT_BANK_PAY;
}
@ -558,4 +558,17 @@ class GoCardlessPaymentDriver extends BaseDriver
{
return render('gateways.gocardless.verification');
}
public function auth(): bool
{
try {
$customers = $this->init()->gateway->customers()->list();
return true;
}
catch(\Exception $e){
}
return false;
}
}

View File

@ -420,4 +420,20 @@ class MolliePaymentDriver extends BaseDriver
{
return \number_format((float) $amount, 2, '.', '');
}
public function auth(): bool
{
$this->init();
try {
$p = $this->gateway->payments->page();
return true;
}
catch(\Exception $e){
}
return false;
}
}

View File

@ -561,4 +561,27 @@ class PayPalPPCPPaymentDriver extends BaseDriver
PayPalWebhook::dispatch($request->all(), $request->headers->all(), $this->access_token);
}
public function auth(): bool
{
try {
$this->init()->getClientToken();
return true;
}
catch(\Exception $e) {
}
return false;
}
public function importCustomers()
{
// $response = $this->gatewayRequest('/v1/reporting/transactions', 'get', ['fields' => 'all','page_size' => 500,'start_date' => '2024-02-01T00:00:00-0000', 'end_date' => '2024-03-01T00:00:00-0000']);
// nlog($response->json());
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More