1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-28 12:17:09 +02:00

Merge pull request #6138 from turbo124/v5-stable

v5.2.7
This commit is contained in:
David Bomba 2021-06-27 07:05:28 +10:00 committed by GitHub
commit 694a704d89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 110082 additions and 109680 deletions

View File

@ -1 +1 @@
5.2.6 5.2.7

View File

@ -60,36 +60,36 @@ class SubdomainFill extends Command
}); });
$db1 = Company::on('db-ninja-01')->get(); // $db1 = Company::on('db-ninja-01')->get();
$db1->each(function ($company){ // $db1->each(function ($company){
$db2 = Company::on('db-ninja-02a')->find($company->id); // $db2 = Company::on('db-ninja-02a')->find($company->id);
if($db2) // if($db2)
{ // {
$db2->subdomain = $company->subdomain; // $db2->subdomain = $company->subdomain;
$db2->save(); // $db2->save();
} // }
}); // });
$db1 = null; // $db1 = null;
$db2 = null; // $db2 = null;
$db2 = Company::on('db-ninja-02')->get(); // $db2 = Company::on('db-ninja-02')->get();
$db2->each(function ($company){ // $db2->each(function ($company){
$db1 = Company::on('db-ninja-01a')->find($company->id); // $db1 = Company::on('db-ninja-01a')->find($company->id);
if($db1) // if($db1)
{ // {
$db1->subdomain = $company->subdomain; // $db1->subdomain = $company->subdomain;
$db1->save(); // $db1->save();
} // }
}); // });
} }
} }

View File

@ -28,7 +28,7 @@ class CompanySettings extends BaseSettings
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented
public $enable_client_portal_tasks = false; //@ben to implement public $enable_client_portal_tasks = false; //@ben to implement
public $show_all_tasks_client_portal = 'all'; // all, uninvoiced, invoiced public $show_all_tasks_client_portal = 'invoiced'; // all, uninvoiced, invoiced
public $enable_client_portal_password = false; //@implemented public $enable_client_portal_password = false; //@implemented
public $enable_client_portal = true; //@implemented public $enable_client_portal = true; //@implemented
public $enable_client_portal_dashboard = false; // @TODO There currently is no dashboard so this is pending public $enable_client_portal_dashboard = false; // @TODO There currently is no dashboard so this is pending
@ -398,7 +398,6 @@ class CompanySettings extends BaseSettings
'email_template_reminder2' => 'string', 'email_template_reminder2' => 'string',
'email_template_reminder3' => 'string', 'email_template_reminder3' => 'string',
'email_template_reminder_endless' => 'string', 'email_template_reminder_endless' => 'string',
'enable_client_portal_password' => 'bool',
'inclusive_taxes' => 'bool', 'inclusive_taxes' => 'bool',
'invoice_number_pattern' => 'string', 'invoice_number_pattern' => 'string',
'invoice_number_counter' => 'integer', 'invoice_number_counter' => 'integer',

View File

@ -77,7 +77,7 @@ class Handler extends ExceptionHandler
return; return;
} }
if(Ninja::isHosted()){ if(Ninja::isHosted() && !($exception instanceof ValidationException)){
app('sentry')->configureScope(function (Scope $scope): void { app('sentry')->configureScope(function (Scope $scope): void {

View File

@ -46,8 +46,6 @@ class GmailTransport extends Transport
$this->gmail = null; $this->gmail = null;
$this->gmail = new Mail; $this->gmail = new Mail;
nlog($message->getBcc());
/*We should nest the token in the message and then discard it as needed*/ /*We should nest the token in the message and then discard it as needed*/
$token = $message->getHeaders()->get('GmailToken')->getValue(); $token = $message->getHeaders()->get('GmailToken')->getValue();
@ -62,13 +60,13 @@ class GmailTransport extends Transport
$this->gmail->message($message->getBody()); $this->gmail->message($message->getBody());
$this->gmail->cc($message->getCc()); $this->gmail->cc($message->getCc());
$this->gmail->bcc($message->getBcc());
if(is_array($message->getBcc()))
$this->gmail->bcc(array_keys($message->getBcc()));
foreach ($message->getChildren() as $child) foreach ($message->getChildren() as $child)
{ {
nlog("trying to attach");
if($child->getContentType() != 'text/plain') if($child->getContentType() != 'text/plain')
{ {

View File

@ -201,6 +201,14 @@ class LoginController extends BaseController
->header('X-Api-Version', config('ninja.minimum_client_version')); ->header('X-Api-Version', config('ninja.minimum_client_version'));
} }
/* If for some reason we lose state on the default company ie. a company is deleted - always make sure we can default to a company*/
if(!$user->account->default_company){
$account = $user->account;
$account->default_company_id = $user->companies->first()->id;
$account->save();
$user = $user->fresh();
}
$user->setCompany($user->account->default_company); $user->setCompany($user->account->default_company);
$this->setLoginCache($user); $this->setLoginCache($user);

View File

@ -21,8 +21,6 @@ class PaymentWebhookController extends Controller
public function __invoke(PaymentWebhookRequest $request, string $company_key, string $company_gateway_id) public function __invoke(PaymentWebhookRequest $request, string $company_key, string $company_gateway_id)
{ {
// MultiDB::findAndSetDbByCompanyKey($company_key);
$payment = $request->getPayment(); $payment = $request->getPayment();
if(!$payment) if(!$payment)
@ -33,7 +31,6 @@ class PaymentWebhookController extends Controller
if(!$client) if(!$client)
return response()->json(['message' => 'Client record not found.'], 400); return response()->json(['message' => 'Client record not found.'], 400);
return $request->getCompanyGateway() return $request->getCompanyGateway()
->driver($client) ->driver($client)
->processWebhookRequest($request, $payment); ->processWebhookRequest($request, $payment);

View File

@ -155,7 +155,7 @@ class PreviewController extends BaseController
$t = app('translator'); $t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings)); $t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
DB::beginTransaction(); DB::connection(config('database.default'))->beginTransaction();
$client = Client::factory()->create([ $client = Client::factory()->create([
'user_id' => auth()->user()->id, 'user_id' => auth()->user()->id,
@ -230,7 +230,7 @@ class PreviewController extends BaseController
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company()); $file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
DB::rollBack(); DB::connection(config('database.default'))->rollBack();
$response = Response::make($file_path, 200); $response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf'); $response->header('Content-Type', 'application/pdf');

View File

@ -11,11 +11,17 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use PragmaRX\Google2FA\Google2FA; use App\Models\User;
use App\Transformers\UserTransformer;
use Crypt; use Crypt;
use PragmaRX\Google2FA\Google2FA;
class TwoFactorController extends BaseController class TwoFactorController extends BaseController
{ {
protected $entity_type = User::class;
protected $entity_transformer = UserTransformer::class;
public function setupTwoFactor() public function setupTwoFactor()
{ {
$user = auth()->user(); $user = auth()->user();

View File

@ -0,0 +1,56 @@
<?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\Http\Livewire\PaymentMethods;
use App\Libraries\MultiDB;
use Livewire\Component;
class UpdateDefaultMethod extends Component
{
/** @var \App\Models\Company */
public $company;
/** @var \App\Models\ClientGatewayToken */
public $token;
/** @var \App\Models\Client */
public $client;
public function mount()
{
$this->company = $this->client->company;
MultiDB::setDb($this->company->db);
$this->is_disabled = $this->token->is_default;
}
public function makeDefault(): void
{
if ($this->token->is_default) {
return;
}
$this->client->gateway_tokens()->update(['is_default' => 0]);
$this->token->is_default = 1;
$this->token->save();
$this->emit('UpdateDefaultMethod::method-updated');
}
public function render()
{
return render('components.livewire.update-default-payment-method');
}
}

View File

@ -0,0 +1,34 @@
<?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\Http\Livewire\RecurringInvoices;
use Livewire\Component;
class UpdateAutoBilling extends Component
{
/** @var \App\Models\RecurringInvoice */
public $invoice;
public function updateAutoBilling(): void
{
if ($this->invoice->auto_bill === 'optin' || $this->invoice->auto_bill === 'optout') {
$this->invoice->auto_bill_enabled = !$this->invoice->auto_bill_enabled;
$this->invoice->save();
}
}
public function render()
{
return render('components.livewire.recurring-invoices-switch-autobilling');
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\ClientPortal\PaymentMethod;
use App\Http\Requests\Request;
use Illuminate\Foundation\Http\FormRequest;
class VerifyPaymentMethodRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'transactions.*' => 'integer'
];
}
}

View File

@ -45,6 +45,7 @@ class RecurringInvoicesCron
if (! config('ninja.db.multi_db_enabled')) { if (! config('ninja.db.multi_db_enabled')) {
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString()) $recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
->whereNull('deleted_at')
->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0') ->where('remaining_cycles', '!=', '0')
->with('company') ->with('company')
@ -66,6 +67,7 @@ class RecurringInvoicesCron
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString()) $recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
->whereNull('deleted_at')
->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0') ->where('remaining_cycles', '!=', '0')
->with('company') ->with('company')
@ -74,7 +76,7 @@ class RecurringInvoicesCron
nlog(now()->format('Y-m-d') . ' Sending Recurring Invoices. Count = '.$recurring_invoices->count().' On Database # '.$db); nlog(now()->format('Y-m-d') . ' Sending Recurring Invoices. Count = '.$recurring_invoices->count().' On Database # '.$db);
$recurring_invoices->each(function ($recurring_invoice, $key) { $recurring_invoices->each(function ($recurring_invoice, $key) {
nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date); nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date ." Recurring #id = ". $recurring_invoice->id);
if (!$recurring_invoice->company->is_disabled) { if (!$recurring_invoice->company->is_disabled) {
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db); SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);

View File

@ -136,6 +136,11 @@ class CreateEntityPdf implements ShouldQueue
$entity_design_id = 2; $entity_design_id = 2;
$design = Design::find($entity_design_id); $design = Design::find($entity_design_id);
/* Catch all in case migration doesn't pass back a valid design */
if(!$design)
$design = Design::find(2);
$html = new HtmlEngine($this->invitation); $html = new HtmlEngine($this->invitation);
if ($design->is_custom) { if ($design->is_custom) {

View File

@ -101,6 +101,7 @@ class NinjaMailerJob implements ShouldQueue
//send email //send email
try { try {
nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString()); nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString());
nlog("Using mailer => ". $this->mailer);
Mail::mailer($this->mailer) Mail::mailer($this->mailer)
->to($this->nmo->to_user->email) ->to($this->nmo->to_user->email)
@ -146,11 +147,7 @@ class NinjaMailerJob implements ShouldQueue
{ {
/* Singletons need to be rebooted each time just in case our Locale is changing*/ /* Singletons need to be rebooted each time just in case our Locale is changing*/
App::forgetInstance('translator'); App::forgetInstance('translator');
// App::forgetInstance('mail.manager'); //singletons must be destroyed!
// App::forgetInstance('mailer');
// App::forgetInstance('laravelgmail');
$t = app('translator'); $t = app('translator');
/* Inject custom translations if any exist */
$t->replace(Ninja::transformTranslations($this->nmo->settings)); $t->replace(Ninja::transformTranslations($this->nmo->settings));
switch ($this->nmo->settings->email_sending_method) { switch ($this->nmo->settings->email_sending_method) {
@ -165,7 +162,6 @@ class NinjaMailerJob implements ShouldQueue
break; break;
} }
(new MailServiceProvider(app()))->register();
} }
private function setGmailMailer() private function setGmailMailer()
@ -182,7 +178,14 @@ class NinjaMailerJob implements ShouldQueue
$google = (new Google())->init(); $google = (new Google())->init();
try{ try{
if ($google->getClient()->isAccessTokenExpired()) {
$google->refreshToken($user);
$user = $user->fresh();
}
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token)); $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->logMailError('Gmail Token Invalid', $this->company->clients()->first());
@ -190,9 +193,7 @@ class NinjaMailerJob implements ShouldQueue
return $this->setMailDriver(); return $this->setMailDriver();
} }
if ($google->getClient()->isAccessTokenExpired()) {
$google->refreshToken($user);
}
/* /*
* Now that our token is refreshed and valid we can boot the * Now that our token is refreshed and valid we can boot the

View File

@ -55,10 +55,15 @@ class CompanySizeCheck implements ShouldQueue
private function check() private function check()
{ {
Company::cursor()->each(function ($company) { Company::cursor()->each(function ($company) {
if ($company->invoices->count() > 1000 || $company->products->count() > 1000 || $company->clients->count() > 1000) {
if ($company->invoices()->count() > 1000 || $company->products()->count() > 1000 || $company->clients()->count() > 1000) {
nlog("Marking company {$company->id} as large");
$company->is_large = true; $company->is_large = true;
$company->save(); $company->save();
} }
}); });
} }
} }

View File

@ -337,6 +337,10 @@ class Import implements ShouldQueue
if(!MultiDB::checkDomainAvailable($data['subdomain'])) if(!MultiDB::checkDomainAvailable($data['subdomain']))
$data['subdomain'] = MultiDB::randomSubdomainGenerator(); $data['subdomain'] = MultiDB::randomSubdomainGenerator();
if(strlen($data['subdomain']) == 0)
$data['subdomain'] = MultiDB::randomSubdomainGenerator();
} }
$rules = (new UpdateCompanyRequest())->rules(); $rules = (new UpdateCompanyRequest())->rules();

View File

@ -56,6 +56,7 @@ class ReminderJob implements ShouldQueue
nlog("Sending invoice reminders " . now()->format('Y-m-d h:i:s')); nlog("Sending invoice reminders " . now()->format('Y-m-d h:i:s'));
Invoice::where('next_send_date', '<=', now()->toDateTimeString()) Invoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNull('deleted_at')
->where('is_deleted', 0) ->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0) ->where('balance', '>', 0)

View File

@ -25,14 +25,21 @@ class ACHVerificationNotification extends Mailable
*/ */
public $company; public $company;
/**
* @var string
*/
public $url;
/** /**
* Create a new message instance. * Create a new message instance.
* *
* @return void * @return void
*/ */
public function __construct(Company $company) public function __construct(Company $company, string $url)
{ {
$this->company = $company; $this->company = $company;
$this->url = $url;
} }
/** /**

View File

@ -36,7 +36,7 @@ class MigrationCompleted extends Mailable
$data['settings'] = $this->company->settings; $data['settings'] = $this->company->settings;
$data['company'] = $this->company->fresh(); $data['company'] = $this->company->fresh();
$data['whitelabel'] = $this->company->account->isPaid() ? true : false; $data['whitelabel'] = $this->company->account->isPaid() ? true : false;
$data['check_data'] = $this->check_data; $data['check_data'] = $this->check_data ?: '';
$data['logo'] = $this->company->present()->logo(); $data['logo'] = $this->company->present()->logo();
$result = $this->from(config('mail.from.address'), config('mail.from.name')) $result = $this->from(config('mail.from.address'), config('mail.from.name'))

View File

@ -49,8 +49,11 @@ class TemplateEmail extends Mailable
public function build() public function build()
{ {
// $template_name = 'email.template.'.$this->build_email->getTemplate(); $template_name = 'email.template.'.$this->build_email->getTemplate();
$template_name = 'email.template.client';
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
$template_name = 'email.template.client';
}
if($this->build_email->getTemplate() == 'custom') { if($this->build_email->getTemplate() == 'custom') {
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->client->getSetting('email_style_custom'))); $this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->client->getSetting('email_style_custom')));
@ -62,10 +65,6 @@ class TemplateEmail extends Mailable
$this->build_email->setBody( $this->build_email->setBody(
DesignHelpers::parseMarkdownToHtml($this->build_email->getBody()) DesignHelpers::parseMarkdownToHtml($this->build_email->getBody())
); );
$this->build_email->setBody(
TemplateEngine::wrapElementsIntoTables('<div id="content-wrapper"></div>', $this->build_email->getBody(), $settings)
);
} }
$company = $this->client->company; $company = $this->client->company;

View File

@ -224,6 +224,13 @@ class RecurringInvoice extends BaseModel
$offset = $this->client->timezone_offset(); $offset = $this->client->timezone_offset();
/*
As we are firing at UTC+0 if our offset is negative it is technically firing the day before so we always need
to add ON a day - a day = 86400 seconds
*/
if($offset < 0)
$offset += 86400;
switch ($this->frequency_id) { switch ($this->frequency_id) {
case self::FREQUENCY_DAILY: case self::FREQUENCY_DAILY:
return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset); return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset);
@ -428,17 +435,14 @@ class RecurringInvoice extends BaseModel
'due_date' => $next_due_date_string 'due_date' => $next_due_date_string
]; ];
$next_send_date = $this->nextDateByFrequency($next_send_date->format('Y-m-d')); /* Fixes the timeshift in case the offset is negative which cause a infinite loop due to UTC +0*/
if($this->client->timezone_offset() < 0){
$next_send_date = $this->nextDateByFrequency($next_send_date->addDay()->format('Y-m-d'));
}
else
$next_send_date = $this->nextDateByFrequency($next_send_date->format('Y-m-d'));
} }
/*If no due date is set - unset the due_date value */
// if(!$this->due_date_days || $this->due_date_days == 0){
// foreach($data as $key => $value)
// $data[$key]['due_date'] = '';
// }
return $data; return $data;
} }

View File

@ -211,7 +211,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
return new Item([ return new Item([
'name' => $lineItem->product_key, 'name' => $lineItem->product_key,
'description' => substr($lineItem->notes, 0, 100), 'description' => substr(strip_tags($lineItem->notes), 0, 100),
'price' => $lineItem->cost, 'price' => $lineItem->cost,
'quantity' => $lineItem->quantity, 'quantity' => $lineItem->quantity,
]); ]);

View File

@ -13,6 +13,7 @@
namespace App\PaymentDrivers\Stripe; namespace App\PaymentDrivers\Stripe;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\PaymentMethod\VerifyPaymentMethodRequest;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Mail\NinjaMailerObject;
@ -67,7 +68,7 @@ class ACH
$client_gateway_token = $this->storePaymentMethod($source, $request->input('method'), $customer); $client_gateway_token = $this->storePaymentMethod($source, $request->input('method'), $customer);
$mailer = new NinjaMailerObject(); $mailer = new NinjaMailerObject();
$mailer->mailable = new ACHVerificationNotification(auth('contact')->user()->client->company); $mailer->mailable = new ACHVerificationNotification(auth('contact')->user()->client->company, route('client.payment_methods.verification', ['payment_method' => $client_gateway_token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]));
$mailer->company = auth('contact')->user()->client->company; $mailer->company = auth('contact')->user()->client->company;
$mailer->settings = auth('contact')->user()->client->company->settings; $mailer->settings = auth('contact')->user()->client->company->settings;
$mailer->to_user = auth('contact')->user(); $mailer->to_user = auth('contact')->user();
@ -79,6 +80,12 @@ class ACH
public function verificationView(ClientGatewayToken $token) public function verificationView(ClientGatewayToken $token)
{ {
if (isset($token->meta->state) && $token->meta->state === 'authorized') {
return redirect()
->route('client.payment_methods.show', $token->hashed_id)
->with('message', __('texts.payment_method_verified'));
}
$data = [ $data = [
'token' => $token, 'token' => $token,
'gateway' => $this->stripe, 'gateway' => $this->stripe,
@ -87,8 +94,14 @@ class ACH
return render('gateways.stripe.ach.verify', $data); return render('gateways.stripe.ach.verify', $data);
} }
public function processVerification(Request $request, ClientGatewayToken $token) public function processVerification($request, ClientGatewayToken $token)
{ {
if (isset($token->meta->state) && $token->meta->state === 'authorized') {
return redirect()
->route('client.payment_methods.show', $token->hashed_id)
->with('message', __('texts.payment_method_verified'));
}
$this->stripe->init(); $this->stripe->init();
$bank_account = Customer::retrieveSource($request->customer, $request->source, $this->stripe->stripe_connect_auth); $bank_account = Customer::retrieveSource($request->customer, $request->source, $this->stripe->stripe_connect_auth);
@ -96,12 +109,14 @@ class ACH
try { try {
$bank_account->verify(['amounts' => request()->transactions]); $bank_account->verify(['amounts' => request()->transactions]);
$token->meta->verified_at = now(); $meta = $token->meta;
$meta->state = 'authorized';
$token->meta = $meta;
$token->save(); $token->save();
return redirect() return redirect()
->route('client.invoices.index') ->route('client.payment_methods.show', $token->hashed_id)
->with('success', __('texts.payment_method_verified')); ->with('message', __('texts.payment_method_verified'));
} catch (CardException $e) { } catch (CardException $e) {
return back()->with('error', $e->getMessage()); return back()->with('error', $e->getMessage());
} }

View File

@ -161,7 +161,7 @@ class ACH
{ {
$meta = $token->meta; $meta = $token->meta;
$meta->state = $response->state; $meta->state = $response->state;
$token->meta; $token->meta = $meta;
$token->save(); $token->save();
return redirect()->route('client.payment_methods.index'); return redirect()->route('client.payment_methods.index');
@ -189,7 +189,7 @@ class ACH
public function paymentResponse($request) public function paymentResponse($request)
{ {
nlog($request->all()); // nlog($request->all());
$token = ClientGatewayToken::find($this->decodePrimaryKey($request->input('source'))); $token = ClientGatewayToken::find($this->decodePrimaryKey($request->input('source')));
$token_meta = $token->meta; $token_meta = $token->meta;
@ -197,14 +197,20 @@ class ACH
if($token_meta->state != "authorized") if($token_meta->state != "authorized")
return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]); return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]);
$app_fee = (config('ninja.wepay.fee_ach_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed');
$response = $this->wepay_payment_driver->wepay->request('checkout/create', array( $response = $this->wepay_payment_driver->wepay->request('checkout/create', array(
// 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]), // 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]),
'unique_id' => Str::random(40), 'unique_id' => Str::random(40),
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'), 'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee, 'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(), 'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
'short_description' => 'A vacation home rental', 'short_description' => 'Goods and Services',
'type' => 'goods', 'type' => 'goods',
'fee' => [
'fee_payer' => config('ninja.wepay.fee_payer'),
'app_fee' => $app_fee,
],
'payment_method' => array( 'payment_method' => array(
'type' => 'payment_bank', 'type' => 'payment_bank',
'payment_bank' => array( 'payment_bank' => array(

View File

@ -139,16 +139,21 @@ use WePayCommon;
} }
// USD, CAD, and GBP. // USD, CAD, and GBP.
nlog($request->all()); // nlog($request->all());
$app_fee = (config('ninja.wepay.fee_cc_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed');
// charge the credit card // charge the credit card
$response = $this->wepay_payment_driver->wepay->request('checkout/create', array( $response = $this->wepay_payment_driver->wepay->request('checkout/create', array(
// 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]),
'unique_id' => Str::random(40), 'unique_id' => Str::random(40),
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'), 'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee, 'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(), 'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
'short_description' => 'A vacation home rental', 'short_description' => 'Goods and services',
'type' => 'goods', 'type' => 'goods',
'fee' => [
'fee_payer' => config('ninja.wepay.fee_payer'),
'app_fee' => $app_fee,
],
'payment_method' => array( 'payment_method' => array(
'type' => 'credit_card', 'type' => 'credit_card',
'credit_card' => array( 'credit_card' => array(

View File

@ -24,8 +24,13 @@ trait WePayCommon
private function processSuccessfulPayment($response, $payment_status, $gateway_type) private function processSuccessfulPayment($response, $payment_status, $gateway_type)
{ {
if($gateway_type == GatewayType::BANK_TRANSFER)
$payment_type = PaymentType::ACH;
else
$payment_type = PaymentType::CREDIT_CARD_OTHER;
$data = [ $data = [
'payment_type' => PaymentType::CREDIT_CARD_OTHER, 'payment_type' => $payment_type,
'amount' => $response->amount, 'amount' => $response->amount,
'transaction_reference' => $response->checkout_id, 'transaction_reference' => $response->checkout_id,
'gateway_type_id' => $gateway_type, 'gateway_type_id' => $gateway_type,

View File

@ -266,18 +266,17 @@ class WePayPaymentDriver extends BaseDriver
$response = $this->wepay->request('checkout/refund', array( $response = $this->wepay->request('checkout/refund', array(
'checkout_id' => $payment->transaction_reference, 'checkout_id' => $payment->transaction_reference,
'refund_reason' => 'Refund', 'refund_reason' => 'Refund by merchant',
'amount' => $amount 'amount' => $amount
)); ));
return [
return [ 'transaction_reference' => $response->checkout_id,
'transaction_reference' => $response->checkout_id, 'transaction_response' => json_encode($response),
'transaction_response' => json_encode($response), 'success' => $response->state == 'refunded' ? true : false,
'success' => $response->state == 'refunded' ? true : false, 'description' => 'refund',
'description' => 'refund', 'code' => 0,
'code' => 0, ];
];
} }

View File

@ -11,12 +11,14 @@
namespace App\Repositories; namespace App\Repositories;
use App\Models\Client;
use App\Models\GroupSetting; use App\Models\GroupSetting;
class GroupSettingRepository extends BaseRepository class GroupSettingRepository extends BaseRepository
{ {
public function save($data, GroupSetting $group_setting) :?GroupSetting public function save($data, GroupSetting $group_setting) :?GroupSetting
{ {
$group_setting->fill($data); $group_setting->fill($data);
$group_setting->save(); $group_setting->save();
@ -27,6 +29,15 @@ class GroupSettingRepository extends BaseRepository
$group_setting->save(); $group_setting->save();
} }
nlog($data['settings']);
if(count((array)$data['settings']) == 0){
$settings = new \stdClass;
$settings->entity = Client::class;
$group_setting->settings = $settings;
$group_setting->save();
}
return $group_setting; return $group_setting;
} }
} }

View File

@ -208,7 +208,9 @@ class PaymentMigrationRepository extends BaseRepository
$exchange_rate = new CurrencyApi(); $exchange_rate = new CurrencyApi();
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date)); $payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
$payment->exchange_currency_id = $client_currency; // $payment->exchange_currency_id = $client_currency;
$payment->exchange_currency_id = $company_currency;
} }
return $payment; return $payment;

View File

@ -199,7 +199,9 @@ class PaymentRepository extends BaseRepository {
$exchange_rate = new CurrencyApi(); $exchange_rate = new CurrencyApi();
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date)); $payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
$payment->exchange_currency_id = $client_currency; // $payment->exchange_currency_id = $client_currency;
$payment->exchange_currency_id = $company_currency;
} }
return $payment; return $payment;

View File

@ -13,6 +13,7 @@
namespace App\Repositories; namespace App\Repositories;
use App\DataMapper\ClientSettings;
use App\DataMapper\InvoiceItem; use App\DataMapper\InvoiceItem;
use App\Factory\InvoiceFactory; use App\Factory\InvoiceFactory;
use App\Models\Client; use App\Models\Client;
@ -44,8 +45,8 @@ class SubscriptionRepository extends BaseRepository
private function calculatePrice($subscription) :array private function calculatePrice($subscription) :array
{ {
DB::beginTransaction(); // DB::beginTransaction();
DB::connection(config('database.default'))->beginTransaction();
$data = []; $data = [];
$client = Client::factory()->create([ $client = Client::factory()->create([
@ -53,6 +54,7 @@ class SubscriptionRepository extends BaseRepository
'company_id' => $subscription->company_id, 'company_id' => $subscription->company_id,
'group_settings_id' => $subscription->group_id, 'group_settings_id' => $subscription->group_id,
'country_id' => $subscription->company->settings->country_id, 'country_id' => $subscription->company->settings->country_id,
'settings' => ClientSettings::defaults(),
]); ]);
$contact = ClientContact::factory()->create([ $contact = ClientContact::factory()->create([
@ -88,7 +90,8 @@ class SubscriptionRepository extends BaseRepository
$data['promo_price'] = $invoice->calc()->getTotal(); $data['promo_price'] = $invoice->calc()->getTotal();
DB::rollBack(); // DB::rollBack();
DB::connection(config('database.default'))->rollBack();
return $data; return $data;
} }

View File

@ -377,23 +377,28 @@ class InvoiceService
{ {
switch ($reminder_template) { switch ($reminder_template) {
case 'reminder1': case 'reminder1':
$this->invoice->reminder1_sent = now()->format('Y-m-d'); $this->invoice->reminder1_sent = now();
$this->invoice->reminder_last_sent = now()->format('Y-m-d'); $this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break; break;
case 'reminder2': case 'reminder2':
$this->invoice->reminder2_sent = now()->format('Y-m-d'); $this->invoice->reminder2_sent = now();
$this->invoice->reminder_last_sent = now()->format('Y-m-d'); $this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break; break;
case 'reminder3': case 'reminder3':
$this->invoice->reminder3_sent = now()->format('Y-m-d'); $this->invoice->reminder3_sent = now();
$this->invoice->reminder_last_sent = now()->format('Y-m-d'); $this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break; break;
case 'endless_reminder': case 'endless_reminder':
$this->invoice->reminder_last_sent = now()->format('Y-m-d'); $this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break; break;
default: default:
$this->invoice->reminder1_sent = now()->format('Y-m-d'); $this->invoice->reminder1_sent = now();
$this->invoice->reminder_last_sent = now()->format('Y-m-d'); $this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break; break;
} }

View File

@ -113,7 +113,9 @@ class MarkPaid extends AbstractService
$exchange_rate = new CurrencyApi(); $exchange_rate = new CurrencyApi();
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date)); $payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
$payment->exchange_currency_id = $client_currency; //$payment->exchange_currency_id = $client_currency; // 23/06/2021
$payment->exchange_currency_id = $company_currency;
$payment->save(); $payment->save();
} }

View File

@ -12,6 +12,7 @@
namespace App\Services\Invoice; namespace App\Services\Invoice;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Services\AbstractService; use App\Services\AbstractService;
use Carbon\Carbon; use Carbon\Carbon;
@ -126,6 +127,14 @@ class UpdateReminder extends AbstractService
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
} }
if ($this->invoice->last_sent_date &&
(int)$this->settings->endless_reminder_frequency_id > 0) {
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int)$this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date);
}
if($date_collection->count() >=1 && $date_collection->sort()->first()->gte(now())) if($date_collection->count() >=1 && $date_collection->sort()->first()->gte(now()))
$this->invoice->next_send_date = $date_collection->sort()->first(); $this->invoice->next_send_date = $date_collection->sort()->first();
else else
@ -133,4 +142,41 @@ class UpdateReminder extends AbstractService
return $this->invoice; return $this->invoice;
} }
private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon
{
if (!$date)
return null;
switch ($endless_reminder_frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay()->startOfDay();
case RecurringInvoice::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
return Carbon::parse($date)->addWeeks(2)->startOfDay();
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($date)->addWeeks(4)->startOfDay();
case RecurringInvoice::FREQUENCY_MONTHLY:
return Carbon::parse($date)->addMonthNoOverflow()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(2)->startOfDay();
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(3)->startOfDay();
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(4)->startOfDay();
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(6)->startOfDay();
case RecurringInvoice::FREQUENCY_ANNUALLY:
return Carbon::parse($date)->addYear()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_YEARS:
return Carbon::parse($date)->addYears(2)->startOfDay();
case RecurringInvoice::FREQUENCY_THREE_YEARS:
return Carbon::parse($date)->addYears(3)->startOfDay();
default:
return null;
}
}
} }

View File

@ -137,10 +137,6 @@ class Design extends BaseDesign
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
} }
foreach (['company1', 'company2', 'company3', 'company4'] as $field) {
$elements[] = ['element' => 'p', 'content' => $this->getCustomFieldValue($field), 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . $field]];
}
return $elements; return $elements;
} }
@ -443,7 +439,7 @@ class Design extends BaseDesign
['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']],
['element' => 'span', 'content' => strtr($_variables['values']['$entity.terms'], $_variables), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], ['element' => 'span', 'content' => strtr($_variables['values']['$entity.terms'], $_variables), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
]], ]],
['element' => 'img', 'properties' => ['hidden' => $this->client->getSetting('signature_on_pdf'), 'style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature']], ['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']],
['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [ ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [
['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']], ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']],
]], ]],
@ -501,11 +497,11 @@ class Design extends BaseDesign
} elseif (Str::startsWith($variable, '$custom_surcharge')) { } elseif (Str::startsWith($variable, '$custom_surcharge')) {
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1 $_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
$visible = $this->entity->{$_variable} == '0'; $visible = $this->entity->{$_variable} > 0 || $this->entity->{$_variable} > '0';
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [ $elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => $visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => $visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], ['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]],
]]; ]];
} elseif (Str::startsWith($variable, '$custom')) { } elseif (Str::startsWith($variable, '$custom')) {
$field = explode('_', $variable); $field = explode('_', $variable);

View File

@ -109,11 +109,15 @@ class HtmlEngine
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.date')]; $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.date')];
$data['$invoice.date'] = &$data['$date']; $data['$invoice.date'] = &$data['$date'];
$data['$invoiceDate'] = &$data['$date'];
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; $data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
$data['$dueDate'] = &$data['$due_date'];
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.payment_due')]; $data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.payment_due')];
$data['$invoice.due_date'] = &$data['$due_date']; $data['$invoice.due_date'] = &$data['$due_date'];
$data['$invoice.number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.invoice_number')]; $data['$invoice.number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.invoice_number')];
$data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: '&nbsp;', 'label' => ctrans('texts.po_number')]; $data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: '&nbsp;', 'label' => ctrans('texts.po_number')];
$data['$poNumber'] = &$data['$invoice.po_number'];
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->entity->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.date')]; $data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->entity->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.date')];
$data['$invoice.datetime'] = &$data['$entity.datetime']; $data['$invoice.datetime'] = &$data['$entity.datetime'];
$data['$quote.datetime'] = &$data['$entity.datetime']; $data['$quote.datetime'] = &$data['$entity.datetime'];
@ -125,6 +129,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.invoice_terms')]; $data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.invoice_terms')];
$data['$terms'] = &$data['$entity.terms']; $data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')]; $data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
$data['$viewLink'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.invoice_date')]; $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.invoice_date')];
@ -139,6 +144,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.quote_terms')]; $data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.quote_terms')];
$data['$terms'] = &$data['$entity.terms']; $data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')]; $data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')];
$data['$viewLink'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')]; $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.quote_date')]; $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.quote_date')];
} }
@ -149,6 +155,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.credit_terms')]; $data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.credit_terms')];
$data['$terms'] = &$data['$entity.terms']; $data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')]; $data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')];
$data['$viewLink'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')]; $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')]; // $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.credit_date')]; $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.credit_date')];
@ -171,11 +178,13 @@ class HtmlEngine
} }
$data['$quote.balance_due'] = $data['$balance_due']; $data['$quote.balance_due'] = &$data['$balance_due'];
$data['$invoice.balance_due'] = $data['$balance_due']; $data['$invoice.balance_due'] = &$data['$balance_due'];
// $data['$balance_due'] = $data['$balance_due']; // $data['$balance_due'] = $data['$balance_due'];
$data['$outstanding'] = $data['$balance_due']; $data['$outstanding'] = &$data['$balance_due'];
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: '&nbsp;', 'label' => ctrans('texts.partial_due')]; $data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: '&nbsp;', 'label' => ctrans('texts.partial_due')];
$data['$partial'] = &$data['$partial_due'];
$data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.total')]; $data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.total')];
$data['$amount'] = &$data['$total']; $data['$amount'] = &$data['$total'];
$data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')]; $data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')];
@ -205,6 +214,7 @@ class HtmlEngine
$data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: '', 'label' => ctrans('texts.public_notes')]; $data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: '', 'label' => ctrans('texts.public_notes')];
$data['$entity.public_notes'] = &$data['$invoice.public_notes']; $data['$entity.public_notes'] = &$data['$invoice.public_notes'];
$data['$public_notes'] = &$data['$invoice.public_notes']; $data['$public_notes'] = &$data['$invoice.public_notes'];
$data['$notes'] = &$data['$public_notes'];
$data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")]; $data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")];
$data['$your_entity'] = ['value' => '', 'label' => ctrans("texts.your_{$this->entity_string}")]; $data['$your_entity'] = ['value' => '', 'label' => ctrans("texts.your_{$this->entity_string}")];
@ -249,6 +259,8 @@ class HtmlEngine
$data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')]; $data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')];
$data['$client_name'] = ['value' => $this->entity->present()->clientName() ?: '&nbsp;', 'label' => ctrans('texts.client_name')]; $data['$client_name'] = ['value' => $this->entity->present()->clientName() ?: '&nbsp;', 'label' => ctrans('texts.client_name')];
$data['$client.name'] = &$data['$client_name']; $data['$client.name'] = &$data['$client_name'];
$data['$client'] = &$data['$client_name'];
$data['$client.address1'] = &$data['$address1']; $data['$client.address1'] = &$data['$address1'];
$data['$client.address2'] = &$data['$address2']; $data['$client.address2'] = &$data['$address2'];
$data['$client_address'] = ['value' => $this->client->present()->address() ?: '&nbsp;', 'label' => ctrans('texts.address')]; $data['$client_address'] = ['value' => $this->client->present()->address() ?: '&nbsp;', 'label' => ctrans('texts.address')];
@ -272,11 +284,15 @@ class HtmlEngine
$data['$paid_to_date'] = ['value' => Number::formatMoney($this->entity->paid_to_date, $this->client), 'label' => ctrans('texts.paid_to_date')]; $data['$paid_to_date'] = ['value' => Number::formatMoney($this->entity->paid_to_date, $this->client), 'label' => ctrans('texts.paid_to_date')];
$data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')]; $data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')];
$data['$contact'] = &$data['$contact.full_name'];
$data['$contact.email'] = ['value' => $this->contact->email, 'label' => ctrans('texts.email')]; $data['$contact.email'] = ['value' => $this->contact->email, 'label' => ctrans('texts.email')];
$data['$contact.phone'] = ['value' => $this->contact->phone, 'label' => ctrans('texts.phone')]; $data['$contact.phone'] = ['value' => $this->contact->phone, 'label' => ctrans('texts.phone')];
$data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : $this->client->present()->name(), 'label' => ctrans('texts.contact_name')]; $data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : $this->client->present()->name(), 'label' => ctrans('texts.contact_name')];
$data['$contact.first_name'] = ['value' => isset($this->contact) ? $this->contact->first_name : '', 'label' => ctrans('texts.first_name')]; $data['$contact.first_name'] = ['value' => isset($this->contact) ? $this->contact->first_name : '', 'label' => ctrans('texts.first_name')];
$data['$firstName'] = &$data['$contact.first_name'];
$data['$contact.last_name'] = ['value' => isset($this->contact) ? $this->contact->last_name : '', 'label' => ctrans('texts.last_name')]; $data['$contact.last_name'] = ['value' => isset($this->contact) ? $this->contact->last_name : '', 'label' => ctrans('texts.last_name')];
@ -288,6 +304,8 @@ class HtmlEngine
$data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: '&nbsp;', 'label' => ctrans('texts.city_state_postal')]; $data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: '&nbsp;', 'label' => ctrans('texts.city_state_postal')];
$data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')]; $data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$company.name'] = ['value' => $this->settings->name ?: ctrans('texts.untitled_account'), 'label' => ctrans('texts.company_name')]; $data['$company.name'] = ['value' => $this->settings->name ?: ctrans('texts.untitled_account'), 'label' => ctrans('texts.company_name')];
$data['$account'] = &$data['$company.name'];
$data['$company.address1'] = ['value' => $this->settings->address1 ?: '&nbsp;', 'label' => ctrans('texts.address1')]; $data['$company.address1'] = ['value' => $this->settings->address1 ?: '&nbsp;', 'label' => ctrans('texts.address1')];
$data['$company.address2'] = ['value' => $this->settings->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')]; $data['$company.address2'] = ['value' => $this->settings->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')];
$data['$company.city'] = ['value' => $this->settings->city ?: '&nbsp;', 'label' => ctrans('texts.city')]; $data['$company.city'] = ['value' => $this->settings->city ?: '&nbsp;', 'label' => ctrans('texts.city')];
@ -302,6 +320,7 @@ class HtmlEngine
$data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: '&nbsp;', 'label' => ctrans('texts.address')]; $data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: '&nbsp;', 'label' => ctrans('texts.address')];
$data['$signature'] = ['value' => $this->settings->email_signature ?: '&nbsp;', 'label' => '']; $data['$signature'] = ['value' => $this->settings->email_signature ?: '&nbsp;', 'label' => ''];
$data['$emailSignature'] = &$data['$signature'];
$data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance, $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client)), 'label' => '']; $data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance, $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client)), 'label' => ''];
@ -393,6 +412,8 @@ class HtmlEngine
$data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => '']; $data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => ''];
$data['$tech_hero_image'] = ['value' => asset('images/pdf-designs/tech-hero-image.jpg'), 'label' => '']; $data['$tech_hero_image'] = ['value' => asset('images/pdf-designs/tech-hero-image.jpg'), 'label' => ''];
$data['$autoBill'] = ['value' => ctrans('texts.auto_bill_notification_placeholder'), 'label' => ''];
$data['$auto_bill'] = &$data['$autoBill'];
$arrKeysLength = array_map('strlen', array_keys($data)); $arrKeysLength = array_map('strlen', array_keys($data));
array_multisort($arrKeysLength, SORT_DESC, $data); array_multisort($arrKeysLength, SORT_DESC, $data);

View File

@ -226,7 +226,7 @@ class TemplateEngine
$data = [ $data = [
'subject' => $this->subject, 'subject' => $this->subject,
'body' => $email_style == 'custom' ? $this->body : self::wrapElementsIntoTables(strtr('<div id="content-wrapper"></div>', ['$body' => '']), $this->body, $this->entity_obj->client->getMergedSettings()), 'body' => $this->body,
'wrapper' => $wrapper, 'wrapper' => $wrapper,
'raw_body' => $this->raw_body, 'raw_body' => $this->raw_body,
'raw_subject' => $this->raw_subject 'raw_subject' => $this->raw_subject
@ -239,7 +239,7 @@ class TemplateEngine
private function mockEntity() private function mockEntity()
{ {
DB::beginTransaction(); DB::connection(config('database.default'))->beginTransaction();
$client = Client::factory()->create([ $client = Client::factory()->create([
'user_id' => auth()->user()->id, 'user_id' => auth()->user()->id,
@ -277,52 +277,6 @@ class TemplateEngine
private function tearDown() private function tearDown()
{ {
DB::rollBack(); DB::connection(config('database.default'))->rollBack();
}
public static function wrapElementsIntoTables(string $wrapper, string $body, $settings): ?string
{
$documents['wrapper'] = new \DOMDocument();
@$documents['wrapper']->loadHTML($wrapper);
$documents['master'] = new \DOMDocument();
$documents['master']->loadHTML(
view('email.template.master', ['header' => '', 'slot' => '', 'settings' => $settings])->render()
);
$styles = $documents['master']->getElementsByTagName('style')->item(0)->nodeValue;
$documents['wrapper']->saveHTML();
$documents['body'] = new \DOMDocument();
$documents['body']->loadHTML(empty($body) ? '<div></div>' : mb_convert_encoding((new CssToInlineStyles())->convert($body, $styles), 'HTML-ENTITIES', 'UTF-8'));
$table_html ='
<table style="font-family:arial,helvetica,sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
<tbody>
<tr>
<td style="overflow-wrap:break-word;word-break:break-word;padding:5px;font-family:arial,helvetica,sans-serif;" align="left">
<div style="color: #000000; line-height: 140%; text-align: left; word-wrap: break-word;" id="table-content" class="content-contrast-color"></div>
</td>
</tr>
</tbody>
</table>';
foreach ($documents['body']->getElementsByTagName('body')->item(0)->childNodes as $element) {
$table = new \DOMDocument();
$table->loadHTML($table_html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$element = $table->importNode($element, true);
$table->getElementById('table-content')->appendChild($element);
$node = $documents['wrapper']->importNode($table->documentElement, true);
$documents['wrapper']->getElementById('content-wrapper')->appendChild($node);
}
return $documents['wrapper']->getElementById('content-wrapper')->ownerDocument->saveHTML($documents['wrapper']->getElementById('content-wrapper'));
} }
} }

View File

@ -568,13 +568,21 @@ trait GeneratesCounter
$search[] = '{$client_counter}'; $search[] = '{$client_counter}';
$replace[] = $counter; $replace[] = $counter;
$search[] = '{$clientCounter}';
$replace[] = $counter;
$search[] = '{$group_counter}'; $search[] = '{$group_counter}';
$replace[] = $counter; $replace[] = $counter;
if (strstr($pattern, '{$user_id}')) { $search[] = '{$year}';
$replace[] = date('Y');
if (strstr($pattern, '{$user_id}') || strstr($pattern, '{$userId}')) {
$user_id = $entity->user_id ? $entity->user_id : 0; $user_id = $entity->user_id ? $entity->user_id : 0;
$search[] = '{$user_id}'; $search[] = '{$user_id}';
$replace[] = str_pad(($user_id), 2, '0', STR_PAD_LEFT); $replace[] = str_pad(($user_id), 2, '0', STR_PAD_LEFT);
$search[] = '{$userId}';
$replace[] = str_pad(($user_id), 2, '0', STR_PAD_LEFT);
} }
$matches = false; $matches = false;
@ -624,9 +632,15 @@ trait GeneratesCounter
$search[] = '{$client_custom1}'; $search[] = '{$client_custom1}';
$replace[] = $client->custom_value1; $replace[] = $client->custom_value1;
$search[] = '{$clientCustom1}';
$replace[] = $client->custom_value1;
$search[] = '{$client_custom2}'; $search[] = '{$client_custom2}';
$replace[] = $client->custom_value2; $replace[] = $client->custom_value2;
$search[] = '{$clientCustom2}';
$replace[] = $client->custom_value2;
$search[] = '{$client_custom3}'; $search[] = '{$client_custom3}';
$replace[] = $client->custom_value3; $replace[] = $client->custom_value3;
@ -638,6 +652,9 @@ trait GeneratesCounter
$search[] = '{$client_id_number}'; $search[] = '{$client_id_number}';
$replace[] = $client->id_number; $replace[] = $client->id_number;
$search[] = '{$clientIdNumber}';
$replace[] = $client->id_number;
} }
return str_replace($search, $replace, $pattern); return str_replace($search, $replace, $pattern);

View File

@ -474,7 +474,7 @@ trait MakesInvoiceValues
} }
if ($matches->keys()->first() == ':MONTH') { if ($matches->keys()->first() == ':MONTH') {
$output = \Carbon\Carbon::create()->month($output)->localeMonth; $output = \Carbon\Carbon::create()->month($output)->translatedFormat('F');
} }
$value = preg_replace( $value = preg_replace(

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.2.6', 'app_version' => '5.2.7',
'app_tag' => '5.2.6-release', 'app_tag' => '5.2.7-release',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),
@ -152,6 +152,10 @@ return [
'environment' => env('WEPAY_ENVIRONMENT', 'stage'), 'environment' => env('WEPAY_ENVIRONMENT', 'stage'),
'client_id' => env('WEPAY_CLIENT_ID', ''), 'client_id' => env('WEPAY_CLIENT_ID', ''),
'client_secret' => env('WEPAY_CLIENT_SECRET',''), 'client_secret' => env('WEPAY_CLIENT_SECRET',''),
'fee_payer' => env('WEPAY_FEE_PAYER'),
'fee_cc_multiplier' => env('WEPAY_APP_FEE_CC_MULTIPLIER'),
'fee_ach_multiplier' => env('WEPAY_APP_FEE_ACH_MULTIPLIER'),
'fee_fixed' => env('WEPAY_APP_FEE_FIXED'),
], ],
'ninja_stripe_publishable_key' => env('NINJA_PUBLISHABLE_KEY', null), 'ninja_stripe_publishable_key' => env('NINJA_PUBLISHABLE_KEY', null),
'ninja_stripe_client_id' => env('NINJA_STRIPE_CLIENT_ID', null), 'ninja_stripe_client_id' => env('NINJA_STRIPE_CLIENT_ID', null),

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class PaymentsTableCurrencyNullable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('payments', function (Blueprint $table){
$table->unsignedInteger('exchange_currency_id')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ const RESOURCES = {
"assets/NOTICES": "687b68d41e137cfbdee105c0b9be3e9d", "assets/NOTICES": "687b68d41e137cfbdee105c0b9be3e9d",
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f", "assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
"favicon.png": "dca91c54388f52eded692718d5a98b8b", "favicon.png": "dca91c54388f52eded692718d5a98b8b",
"main.dart.js": "1225d7da63eaf3817e25a8b6726635cc", "main.dart.js": "383f48eff49849cbbe38e2fe4ae81ad4",
"/": "23224b5e03519aaa87594403d54412cf" "/": "23224b5e03519aaa87594403d54412cf"
}; };

112046
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

106878
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5", "/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
"/css/app.css": "/css/app.css?id=b3b5fbe4dbfcef5b5f8e", "/css/app.css": "/css/app.css?id=d9b987796d537e68bee7",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4", "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1", "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7", "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",

View File

@ -4268,6 +4268,10 @@ $LANG = array(
'company_import_failure_body' => 'There was an error importing the company data, the error message was:', 'company_import_failure_body' => 'There was an error importing the company data, the error message was:',
'recurring_invoice_due_date' => 'Due Date', 'recurring_invoice_due_date' => 'Due Date',
'amount_cents' => 'Amount in pennies,pence or cents', 'amount_cents' => 'Amount in pennies,pence or cents',
'default_payment_method_label' => 'Default Payment Method',
'default_payment_method' => 'Make this your preferred way of paying.',
'already_default_payment_method' => 'This is your preferred way of paying.',
'auto_bill_disabled' => 'Auto Bill Disabled',
); );
return $LANG; return $LANG;

View File

@ -236,7 +236,7 @@ $LANG = array(
'archived_vendors' => 'Successfully archived :count vendors', 'archived_vendors' => 'Successfully archived :count vendors',
'deleted_vendor' => 'Successfully deleted vendor', 'deleted_vendor' => 'Successfully deleted vendor',
'deleted_vendors' => 'Successfully deleted :count vendors', 'deleted_vendors' => 'Successfully deleted :count vendors',
'confirmation_subject' => 'Invoice Ninja Account Confirmation', 'confirmation_subject' => 'Account Confirmation',
'confirmation_header' => 'Account Confirmation', 'confirmation_header' => 'Account Confirmation',
'confirmation_message' => 'Please access the link below to confirm your account.', 'confirmation_message' => 'Please access the link below to confirm your account.',
'invoice_subject' => 'New invoice :number from :account', 'invoice_subject' => 'New invoice :number from :account',
@ -887,7 +887,7 @@ $LANG = array(
'custom_invoice_charges_helps' => 'Add a field when creating an invoice and include the charge in the invoice subtotals.', 'custom_invoice_charges_helps' => 'Add a field when creating an invoice and include the charge in the invoice subtotals.',
'token_expired' => 'Validation token was expired. Please try again.', 'token_expired' => 'Validation token was expired. Please try again.',
'invoice_link' => 'Invoice Link', 'invoice_link' => 'Invoice Link',
'button_confirmation_message' => 'Click to confirm your email address.', 'button_confirmation_message' => 'Click to confirm your email.',
'confirm' => 'Confirm', 'confirm' => 'Confirm',
'email_preferences' => 'Email Preferences', 'email_preferences' => 'Email Preferences',
'created_invoices' => 'Successfully created :count invoice(s)', 'created_invoices' => 'Successfully created :count invoice(s)',

View File

@ -25,7 +25,7 @@
@endisset @endisset
@isset($signature) @isset($signature)
{!! $signature !!} {!! nl2br($signature) !!}
@endisset @endisset
</div> </div>
@endcomponent @endcomponent

View File

@ -2,5 +2,7 @@
<div class="center"> <div class="center">
<h1>{{ ctrans('texts.ach_verification_notification_label') }}</h1> <h1>{{ ctrans('texts.ach_verification_notification_label') }}</h1>
<p>{{ ctrans('texts.ach_verification_notification') }}</p> <p>{{ ctrans('texts.ach_verification_notification') }}</p>
<a class="button" href="{{ $url }}">{{ ctrans('texts.complete_verification') }}</a>
</div> </div>
@endcomponent @endcomponent

View File

@ -67,7 +67,7 @@
<p><b>{{ ctrans('texts.documents') }}:</b> {{ count($company->documents) }} </p> <p><b>{{ ctrans('texts.documents') }}:</b> {{ count($company->documents) }} </p>
@endif @endif
@if($check_data) @if(isset($check_data))
<p><b>Data Quality:</b></p> <p><b>Data Quality:</b></p>
<p> {!! $check_data !!} </p> <p> {!! $check_data !!} </p>
@endif @endif

View File

@ -90,6 +90,10 @@
#content .center { #content .center {
text-align: center; text-align: center;
} }
#content .left {
text-align: left !important;
}
</style> </style>
</head> </head>
@ -116,16 +120,12 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<div style="border: 1px solid #c2c2c2; border-top: none; border-bottom: none; padding: 20px;" id="content"> <div style="border: 1px solid #c2c2c2; border-top: none; border-bottom: none; padding: 20px; text-align: center" id="content">
<div style="padding-top: 10px;"></div> <div style="padding-top: 10px;"></div>
{{ $slot ?? '' }} {{ $slot ?? '' }}
{!! $body ?? '' !!} {!! $body ?? '' !!}
@isset($signature)
{{ nl2br($signature) }}
@endisset
<div> <div>
<a href="#" <a href="#"
style="display: inline-block;background-color: {{ $primary_color }}; color: #ffffff; text-transform: uppercase;letter-spacing: 2px; text-decoration: none; font-size: 13px; font-weight: 600;"> style="display: inline-block;background-color: {{ $primary_color }}; color: #ffffff; text-transform: uppercase;letter-spacing: 2px; text-decoration: none; font-size: 13px; font-weight: 600;">
@ -135,24 +135,27 @@
</td> </td>
</tr> </tr>
@if(isset($company) && $company instanceof \App\Models\Company)
<tr> <tr>
<td> <td>
<div class="dark-bg dark-text-white" <div class="dark-bg dark-text-white"
style="text-align: center; padding-top: 10px; padding-bottom: 25px; background-color: #f9f9f9; border: 1px solid #c2c2c2; border-top: none; border-bottom-color: #f9f9f9;"> style="text-align: center; padding-top: 10px; padding-bottom: 25px; background-color: #f9f9f9; border: 1px solid #c2c2c2; border-top: none; border-bottom-color: #f9f9f9;">
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-bottom: 30px;"> @isset($signature)
{{ ctrans('texts.client_email_company_contact_label') }} <p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-bottom: 30px;">
</p> {!! nl2br($signature) !!}
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 500; margin-bottom:0;"> </p>
{{ $company->present()->name() }}</p> @endisset
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-top: 5px;">
<span>{{ $company->settings->phone }}</span> @if(isset($company) && $company instanceof \App\Models\Company)
<span style="font-weight: 500"> {{ $company->settings->website }}</span> <p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 500; margin-bottom:0;">
</p> {{ $company->present()->name() }}</p>
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-top: 5px;">
<span>{{ $company->settings->phone }}</span>
<span style="font-weight: 500"> {{ $company->settings->website }}</span>
</p>
@endif
</div> </div>
</td> </td>
</tr> </tr>
@endif
<tr> <tr>
<td> <td>

View File

@ -37,7 +37,7 @@
flex-direction: column; flex-direction: column;
} }
#company-details > span:first-child { #company-details > p:first-child {
color: var(--primary-color); color: var(--primary-color);
} }

View File

@ -1,6 +1,6 @@
<div class="grid grid-cols-12"> <div class="grid grid-cols-12">
<div class="col-span-12 lg:col-span-6 bg-gray-50 flex flex-col items-center"> <div class="col-span-12 lg:col-span-6 bg-gray-50 flex flex-col items-center">
<div class="w-full p-10 lg:w-1/2 lg:mt-24 lg:p-0"> <div class="w-full p-10 lg:mt-24 md:max-w-3xl">
<img class="h-8" src="{{ $subscription->company->present()->logo }}" <img class="h-8" src="{{ $subscription->company->present()->logo }}"
alt="{{ $subscription->company->present()->name }}"> alt="{{ $subscription->company->present()->name }}">

View File

@ -0,0 +1,8 @@
<label class="flex items-center cursor-pointer">
<input type="checkbox" class="form-checkbox mr-2"
wire:change="updateAutoBilling" {{ $invoice->auto_bill_enabled ? 'checked' : '' }}>
<span class="text-sm leading-5 font-medium text-gray-900">
{{ $invoice->auto_bill_enabled ? ctrans('texts.auto_bill_enabled') : ctrans('texts.auto_bill_disabled') }}
</span>
</label>

View File

@ -0,0 +1,25 @@
<div class="mt-4 mb-4 bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-start sm:justify-between">
<div>
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ ctrans('texts.default_payment_method_label') }}
</h3>
<div class="max-w-xl mt-2 text-sm leading-5 text-gray-500 flex items-center">
<span class="text-primary mr-1 hidden" data-ref="success-label">{{ ctrans('texts.success') }}!</span>
<p>
{{ $token->is_default ? ctrans('texts.already_default_payment_method') : ctrans('texts.default_payment_method') }}
</p>
</div>
</div>
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
<form wire:submit.prevent="makeDefault">
<button class="button button-primary bg-primary" {{ $token->is_default ? 'disabled' : '' }}>
{{ ctrans('texts.save_as_default') }}
</button>
</form>
</div>
</div>
</div>
</div>

View File

@ -15,59 +15,59 @@
<div> <div>
<dl> <dl>
@if(!empty($payment_method->gateway_type->name) && !is_null($payment_method->gateway_type->name)) @if(!empty($payment_method->gateway_type->name) && !is_null($payment_method->gateway_type->name))
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500"> <dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.payment_type') }} {{ ctrans('texts.payment_type') }}
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ ucfirst($payment_method->gateway_type->name) }} {{ ucfirst($payment_method->gateway_type->name) }}
</dd> </dd>
</div> </div>
@endif @endif
@if(!empty($payment_method->meta) && !is_null($payment_method->meta)) @if(!empty($payment_method->meta) && !is_null($payment_method->meta))
<div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500"> <dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.type') }} {{ ctrans('texts.type') }}
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ optional($payment_method->meta)->brand }} {{ optional($payment_method->meta)->brand }}
{{ optional($payment_method->meta)->scheme }} {{ optional($payment_method->meta)->scheme }}
</dd> </dd>
</div> </div>
@endif @endif
@if(!empty($payment_method->meta->last4) && !is_null($payment_method->meta->last4)) @if(!empty($payment_method->meta->last4) && !is_null($payment_method->meta->last4))
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500"> <dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.card_number') }} {{ ctrans('texts.card_number') }}
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
**** {{ ucfirst($payment_method->meta->last4) }} **** {{ ucfirst($payment_method->meta->last4) }}
</dd> </dd>
</div> </div>
@endif @endif
@if(!empty($payment_method->created_at) && !is_null($payment_method->created_at)) @if(!empty($payment_method->created_at) && !is_null($payment_method->created_at))
<div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500"> <dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.date_created') }} {{ ctrans('texts.date_created') }}
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment_method->formatDateTimestamp($payment_method->created_at, auth()->user()->client->date_format()) }} {{ $payment_method->formatDateTimestamp($payment_method->created_at, auth()->user()->client->date_format()) }}
</dd> </dd>
</div> </div>
@endif @endif
@if(!empty($payment_method->is_default) && !is_null($payment_method->is_default)) @if(!empty($payment_method->is_default) && !is_null($payment_method->is_default))
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500"> <dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.default') }} {{ ctrans('texts.default') }}
</dt> </dt>
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment_method->is_default ? ctrans('texts.yes') : ctrans('texts.no') }} {{ $payment_method->is_default ? ctrans('texts.yes') : ctrans('texts.no') }}
</div>
</div> </div>
</div>
@endif @endif
@isset($payment_method->meta->exp_month) @isset($payment_method->meta->exp_month)
@ -83,7 +83,10 @@
</dl> </dl>
</div> </div>
</div> </div>
<div class="mt-4 mb-4 bg-white shadow sm:rounded-lg" translate>
@livewire('payment-methods.update-default-method', ['token' => $payment_method, 'client' => $client])
<div class="mt-4 mb-4 bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6"> <div class="px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-start sm:justify-between"> <div class="sm:flex sm:items-start sm:justify-between">
<div> <div>
@ -109,3 +112,11 @@
</div> </div>
</div> </div>
@endsection @endsection
@section('footer')
<script>
Livewire.on('UpdateDefaultMethod::method-updated', event => {
document.querySelector('span[data-ref=success-label]').classList.remove('hidden');
});
</script>
@endsection

View File

@ -59,6 +59,21 @@
</div> </div>
</div> </div>
@if($invoice->auto_bill === 'optin' || $invoice->auto_bill === 'optout')
<div class="bg-white shadow overflow-hidden lg:rounded-lg mt-4">
<div class="flex flex-col md:flex-row items-start justify-between px-4 py-5 sm:p-6">
<div>
<h3 class="text-lg leading-6 font-medium text-gray-900">Auto Bill</h3>
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">Change your update bill preferences.</p>
</div>
<div class="flex mt-4 space-x-2">
@livewire('recurring-invoices.update-auto-billing', ['invoice' => $invoice])
</div>
</div>
</div>
@endif
@if(is_null($invoice->subscription_id) || optional($invoice->subscription)->allow_cancellation) @if(is_null($invoice->subscription_id) || optional($invoice->subscription)->allow_cancellation)
<div class="bg-white shadow sm:rounded-lg mt-4"> <div class="bg-white shadow sm:rounded-lg mt-4">
<div class="px-4 py-5 sm:p-6"> <div class="px-4 py-5 sm:p-6">