Merge pull request #6765 from turbo124/v5-stable

v5.3.19
This commit is contained in:
David Bomba 2021-10-04 21:15:30 +11:00 committed by GitHub
commit b81b2b5798
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 348170 additions and 347380 deletions

View File

@ -1 +1 @@
5.3.18 5.3.19

View File

@ -50,7 +50,7 @@ class PostUpdate extends Command
info("I wasn't able to migrate the data."); info("I wasn't able to migrate the data.");
} }
nlog("finished migrating"); info("finished migrating");
$output = []; $output = [];

View File

@ -63,7 +63,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping(); $schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
$schedule->job(new RecurringExpensesCron)->dailyAt('23:45')->withoutOverlapping(); $schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping();
$schedule->job(new AutoBillCron)->dailyAt('00:30')->withoutOverlapping(); $schedule->job(new AutoBillCron)->dailyAt('00:30')->withoutOverlapping();

View File

@ -137,7 +137,11 @@ class InvitationController extends Controller
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; $entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
$invitation = $entity_obj::whereRaw('BINARY `key`= ?', [$invitation_key]) // $invitation = $entity_obj::whereRaw('BINARY `key`= ?', [$invitation_key])
// ->with('contact.client')
// ->firstOrFail();
$invitation = $entity_obj::where('key', $invitation_key)
->with('contact.client') ->with('contact.client')
->firstOrFail(); ->firstOrFail();

View File

@ -15,6 +15,7 @@ namespace App\Http\Controllers\ClientPortal;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\Uploads\StoreUploadRequest; use App\Http\Requests\ClientPortal\Uploads\StoreUploadRequest;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Company; use App\Models\Company;
use App\Utils\Ninja; use App\Utils\Ninja;
@ -26,14 +27,18 @@ use Illuminate\Support\Facades\Auth;
class NinjaPlanController extends Controller class NinjaPlanController extends Controller
{ {
public function index(string $contact_key, string $company_key) public function index(string $contact_key, string $account_or_company_key)
{ {
MultiDB::findAndSetDbByCompanyKey($company_key); MultiDB::findAndSetDbByCompanyKey($account_or_company_key);
$company = Company::where('company_key', $company_key)->first(); $company = Company::where('company_key', $account_or_company_key)->first();
nlog("Ninja Plan Controller Company key found {$company->company_key}"); if(!$company){
MultiDB::findAndSetDbByAccountKey($account_or_company_key);
$account = Account::where('key', $account_or_company_key)->first();
}
else
$account = $company->account;
$account = $company->account;
if (MultiDB::findAndSetDbByContactKey($contact_key) && $client_contact = ClientContact::where('contact_key', $contact_key)->first()) if (MultiDB::findAndSetDbByContactKey($contact_key) && $client_contact = ClientContact::where('contact_key', $contact_key)->first())
{ {

View File

@ -11,7 +11,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\DataMapper\Analytics\EmailBounce;
use App\DataMapper\Analytics\Mail\EmailBounce; use App\DataMapper\Analytics\Mail\EmailBounce;
use App\DataMapper\Analytics\Mail\EmailSpam; use App\DataMapper\Analytics\Mail\EmailSpam;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;

View File

@ -278,7 +278,7 @@ class SetupController extends Controller
private function testPhantom() private function testPhantom()
{ {
try { try {
$key = config('ninja.phantomjs_pdf_generation'); $key = config('ninja.phantomjs_key');
$url = 'https://www.invoiceninja.org/'; $url = 'https://www.invoiceninja.org/';
$phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$url}%22,renderType:%22pdf%22%7D"; $phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$url}%22,renderType:%22pdf%22%7D";

View File

@ -39,6 +39,7 @@ class CreditsTable extends Component
->where('client_id', auth('contact')->user()->client->id) ->where('client_id', auth('contact')->user()->client->id)
->where('company_id', $this->company->id) ->where('company_id', $this->company->id)
->where('status_id', '<>', Credit::STATUS_DRAFT) ->where('status_id', '<>', Credit::STATUS_DRAFT)
->where('is_deleted', 0)
->where(function ($query){ ->where(function ($query){
$query->whereDate('due_date', '<=', now()) $query->whereDate('due_date', '<=', now())
->orWhereNull('due_date'); ->orWhereNull('due_date');

View File

@ -41,6 +41,7 @@ class PaymentsTable extends Component
{ {
$query = Payment::query() $query = Payment::query()
->with('type', 'client') ->with('type', 'client')
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED])
->where('company_id', $this->company->id) ->where('company_id', $this->company->id)
->where('client_id', auth('contact')->user()->client->id) ->where('client_id', auth('contact')->user()->client->id)
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')

View File

@ -0,0 +1,63 @@
<?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\Jobs\Cron;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use Illuminate\Foundation\Bus\Dispatchable;
class AutoBill
{
use Dispatchable;
public $tries = 1;
public Invoice $invoice;
public string $db;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Invoice $invoice, ?string $db)
{
$this->invoice = $invoice;
$this->db = $db;
}
/**
* Execute the job.
*
* @return void
*/
public function handle() : void
{
set_time_limit(0);
if($this->db)
MultiDB::setDb($this->db);
try{
$this->invoice->service()->autoBill()->save();
}
catch(\Exception $e) {
nlog("Failed to capture payment for {$this->invoice->company_id} - {$this->invoice->number} ->" . $e->getMessage());
}
}
}

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Cron; namespace App\Jobs\Cron;
use App\Jobs\Cron\AutoBill;
use App\Jobs\RecurringInvoice\SendRecurring; use App\Jobs\RecurringInvoice\SendRecurring;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Invoice; use App\Models\Invoice;
@ -52,12 +53,15 @@ class AutoBillCron
->where('auto_bill_enabled', true) ->where('auto_bill_enabled', true)
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('is_deleted', false) ->where('is_deleted', false)
->whereHas('company', function ($query) {
$query->where('is_disabled',0);
})
->with('company'); ->with('company');
nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill"); nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill");
$auto_bill_partial_invoices->cursor()->each(function ($invoice){ $auto_bill_partial_invoices->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice, false); AutoBill::dispatch($invoice, false);
}); });
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now()) $auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
@ -65,12 +69,16 @@ class AutoBillCron
->where('auto_bill_enabled', true) ->where('auto_bill_enabled', true)
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('is_deleted', false) ->where('is_deleted', false)
->whereHas('company', function ($query) {
$query->where('is_disabled',0);
})
->with('company'); ->with('company');
nlog($auto_bill_invoices->count(). " full invoices to auto bill"); nlog($auto_bill_invoices->count(). " full invoices to auto bill");
$auto_bill_invoices->cursor()->each(function ($invoice){ $auto_bill_invoices->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice, false); AutoBill::dispatch($invoice, false);
}); });
@ -85,12 +93,15 @@ class AutoBillCron
->where('auto_bill_enabled', true) ->where('auto_bill_enabled', true)
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('is_deleted', false) ->where('is_deleted', false)
->whereHas('company', function ($query) {
$query->where('is_disabled',0);
})
->with('company'); ->with('company');
nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill db = {$db}"); nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill db = {$db}");
$auto_bill_partial_invoices->cursor()->each(function ($invoice) use($db){ $auto_bill_partial_invoices->cursor()->each(function ($invoice) use($db){
$this->runAutoBiller($invoice, $db); AutoBill::dispatch($invoice, $db);
}); });
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now()) $auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
@ -98,32 +109,19 @@ class AutoBillCron
->where('auto_bill_enabled', true) ->where('auto_bill_enabled', true)
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('is_deleted', false) ->where('is_deleted', false)
->whereHas('company', function ($query) {
$query->where('is_disabled',0);
})
->with('company'); ->with('company');
nlog($auto_bill_invoices->count(). " full invoices to auto bill db = {$db}"); nlog($auto_bill_invoices->count(). " full invoices to auto bill db = {$db}");
$auto_bill_invoices->cursor()->each(function ($invoice) use($db){ $auto_bill_invoices->cursor()->each(function ($invoice) use($db){
$this->runAutoBiller($invoice, $db); AutoBill::dispatch($invoice, $db);
}); });
} }
} }
} }
private function runAutoBiller(Invoice $invoice, $db)
{
info("Firing autobill for {$invoice->company_id} - {$invoice->number}");
try{
if($db)
MultiDB::setDB($db);
$invoice->service()->autoBill()->save();
}
catch(\Exception $e) {
nlog("Failed to capture payment for {$invoice->company_id} - {$invoice->number} ->" . $e->getMessage());
}
}
} }

View File

@ -66,6 +66,9 @@ class RecurringExpensesCron
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0') ->where('remaining_cycles', '!=', '0')
->whereHas('company', function ($query) {
$query->where('is_disabled',0);
})
->with('company') ->with('company')
->cursor(); ->cursor();

View File

@ -53,23 +53,27 @@ class RecurringInvoicesCron
$query->where('is_deleted',0) $query->where('is_deleted',0)
->where('deleted_at', NULL); ->where('deleted_at', NULL);
}) })
->whereHas('company', function ($query) {
$query->where('is_disabled',0);
})
->with('company') ->with('company')
->cursor(); ->cursor();
nlog(now()->format('Y-m-d') . ' Sending Recurring Invoices. Count = '.$recurring_invoices->count()); nlog(now()->format('Y-m-d') . ' Sending Recurring Invoices. Count = '.$recurring_invoices->count());
$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);
if (!$recurring_invoice->company->is_disabled) { nlog("Trying to send {$recurring_invoice->number}");
try{ try{
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db); SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
}
catch(\Exception $e){
nlog("Unable to sending recurring invoice {$recurring_invoice->id}");
}
} }
catch(\Exception $e){
nlog("Unable to sending recurring invoice {$recurring_invoice->id}");
}
}); });
} else { } else {
//multiDB environment, need to //multiDB environment, need to
@ -86,6 +90,9 @@ class RecurringInvoicesCron
$query->where('is_deleted',0) $query->where('is_deleted',0)
->where('deleted_at', NULL); ->where('deleted_at', NULL);
}) })
->whereHas('company', function ($query) {
$query->where('is_disabled',0);
})
->with('company') ->with('company')
->cursor(); ->cursor();
@ -94,15 +101,15 @@ class RecurringInvoicesCron
$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 ." Recurring #id = ". $recurring_invoice->id); 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) { nlog("Trying to send {$recurring_invoice->number}");
try{ try{
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db); SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
} }
catch(\Exception $e){ catch(\Exception $e){
nlog("Unable to sending recurring invoice {$recurring_invoice->id}"); nlog("Unable to sending recurring invoice {$recurring_invoice->id}");
} }
}
}); });
} }
} }

View File

@ -72,6 +72,11 @@ class SendRecurring implements ShouldQueue
// Generate Standard Invoice // Generate Standard Invoice
$invoice = RecurringInvoiceToInvoiceFactory::create($this->recurring_invoice, $this->recurring_invoice->client); $invoice = RecurringInvoiceToInvoiceFactory::create($this->recurring_invoice, $this->recurring_invoice->client);
if($this->recurring_invoice->auto_bill == "always")
$invoice->auto_bill_enabled = true;
elseif($this->recurring_invoice->auto_bill == "off")
$invoice->auto_bill_enabled = false;
$invoice->date = now()->format('Y-m-d'); $invoice->date = now()->format('Y-m-d');
$invoice->due_date = $this->recurring_invoice->calculateDueDate(now()->format('Y-m-d')); $invoice->due_date = $this->recurring_invoice->calculateDueDate(now()->format('Y-m-d'));
$invoice->recurring_id = $this->recurring_invoice->id; $invoice->recurring_id = $this->recurring_invoice->id;

View File

@ -70,6 +70,9 @@ class ReminderJob implements ShouldQueue
$query->where('is_deleted',0) $query->where('is_deleted',0)
->where('deleted_at', NULL); ->where('deleted_at', NULL);
}) })
->whereHas('company', function ($query) {
$query->where('is_disabled',0);
})
->with('invitations')->cursor()->each(function ($invoice) { ->with('invitations')->cursor()->each(function ($invoice) {
if ($invoice->isPayable()) { if ($invoice->isPayable()) {

View File

@ -11,6 +11,7 @@
namespace App\Libraries; namespace App\Libraries;
use App\Models\Account;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Company; use App\Models\Company;
@ -178,7 +179,7 @@ class MultiDB
$current_db = config('database.default'); $current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($ct = ClientContact::on($db)->whereRaw('BINARY `token`= ?', [$token])->first()) { if (ClientContact::on($db)->whereRaw('BINARY `token`= ?', [$token])->exists()) {
self::setDb($db); self::setDb($db);
return true; return true;
} }
@ -230,8 +231,8 @@ class MultiDB
$current_db = config('database.default'); $current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($ct = CompanyToken::on($db)->whereRaw('BINARY `token`= ?', [$token])->first()) { if (CompanyToken::on($db)->whereRaw('BINARY `token`= ?', [$token])->exists()) {
self::setDb($ct->company->db); self::setDb($db);
return true; return true;
} }
} }
@ -246,8 +247,24 @@ class MultiDB
$current_db = config('database.default'); $current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($company = Company::on($db)->where('company_key', $company_key)->first()) { if (Company::on($db)->where('company_key', $company_key)->exists()) {
self::setDb($company->db); self::setDb($db);
return true;
}
}
self::setDB($current_db);
return false;
}
public static function findAndSetDbByAccountKey($account_key) :bool
{
$current_db = config('database.default');
foreach (self::$dbs as $db) {
if (Account::on($db)->where('key', $account_key)->exists()) {
self::setDb($db);
return true; return true;
} }
} }
@ -262,8 +279,8 @@ class MultiDB
$current_db = config('database.default'); $current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($client_contact = ClientContact::on($db)->where('contact_key', $contact_key)->first()) { if (ClientContact::on($db)->where('contact_key', $contact_key)->exists()) {
self::setDb($client_contact->company->db); self::setDb($db);
return true; return true;
} }
} }
@ -278,8 +295,8 @@ class MultiDB
$current_db = config('database.default'); $current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($client = Client::on($db)->where('client_hash', $client_hash)->first()) { if (Client::on($db)->where('client_hash', $client_hash)->exists()) {
self::setDb($client->company->db); self::setDb($db);
return true; return true;
} }
} }
@ -299,7 +316,7 @@ class MultiDB
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($company = Company::on($db)->where($query_array)->first()) { if ($company = Company::on($db)->where($query_array)->first()) {
self::setDb($company->db); self::setDb($db);
return $company; return $company;
} }
} }
@ -315,7 +332,7 @@ class MultiDB
$current_db = config('database.default'); $current_db = config('database.default');
foreach (self::$dbs as $db) { foreach (self::$dbs as $db) {
if ($invite = $class::on($db)->whereRaw('BINARY `key`= ?', [$invitation_key])->first()) { if ($invite = $class::on($db)->whereRaw('BINARY `key`= ?', [$invitation_key])->exists()) {
self::setDb($db); self::setDb($db);
return true; return true;
} }

View File

@ -64,7 +64,7 @@ class SupportMessageSent extends Mailable
$db = str_replace("db-ninja-", "", $company->db); $db = str_replace("db-ninja-", "", $company->db);
$is_large = $company->is_large ? "L" : "S"; $is_large = $company->is_large ? "L" : "S";
$platform = array_key_exists('platform', $this->data) ? $this->data['platform'] : "U"; $platform = array_key_exists('platform', $this->data) ? $this->data['platform'] : "U";
$migrated = strlen($company->company_key) == 32 ? "M" : ""; $migrated = strlen($company->company_key) == 32 ? "M" : "T";
if(Ninja::isHosted()) if(Ninja::isHosted())
$subject = "{$priority}Hosted-{$db}-{$is_large}{$platform}{$migrated} :: {$plan} :: ".date('M jS, g:ia'); $subject = "{$priority}Hosted-{$db}-{$is_large}{$platform}{$migrated} :: {$plan} :: ".date('M jS, g:ia');
@ -75,7 +75,7 @@ class SupportMessageSent extends Mailable
->replyTo($user->email, $user->present()->name()) ->replyTo($user->email, $user->present()->name())
->subject($subject) ->subject($subject)
->view('email.support.message', [ ->view('email.support.message', [
'support_message' => $this->data['support_message'], 'support_message' => $this->data['message'],
'system_info' => $system_info, 'system_info' => $system_info,
'laravel_log' => $log_lines, 'laravel_log' => $log_lines,
'logo' => $company->present()->logo(), 'logo' => $company->present()->logo(),

View File

@ -271,9 +271,12 @@ class Account extends BaseModel
$trial_active = false; $trial_active = false;
//14 day trial
$duration = 60*60*24*14;
if ($trial_plan && $include_trial) { if ($trial_plan && $include_trial) {
$trial_started = $this->trial_started; $trial_started = $this->trial_started;
$trial_expires = Carbon::parse($this->trial_started)->addSeconds($this->trial_duration); $trial_expires = Carbon::parse($this->trial_started)->addSeconds($duration);
if($trial_expires->greaterThan(now())){ if($trial_expires->greaterThan(now())){
$trial_active = true; $trial_active = true;

View File

@ -41,6 +41,16 @@ class ClientPresenter extends EntityPresenter
return $contact_name; return $contact_name;
} }
public function first_name()
{
return $this->entity->primary_contact->first() !== null ? $this->entity->primary_contact->first()->first_name : $this->entity->contacts()->first()->first_name;
}
public function last_name()
{
return $this->entity->primary_contact->first() !== null ? $this->entity->primary_contact->first()->last_name : $this->entity->contacts()->first()->last_name;
}
public function primary_contact_name() public function primary_contact_name()
{ {
return $this->entity->primary_contact->first() !== null ? $this->entity->primary_contact->first()->first_name.' '.$this->entity->primary_contact->first()->last_name : 'No primary contact set'; return $this->entity->primary_contact->first() !== null ? $this->entity->primary_contact->first()->first_name.' '.$this->entity->primary_contact->first()->last_name : 'No primary contact set';

View File

@ -204,7 +204,10 @@ class RecurringExpense extends BaseModel
public function nextDateByFrequency($date) public function nextDateByFrequency($date)
{ {
$offset = $this->client->timezone_offset(); $offset = 0;
if($this->client)
$offset = $this->client->timezone_offset();
switch ($this->frequency_id) { switch ($this->frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY: case RecurringInvoice::FREQUENCY_DAILY:

View File

@ -34,6 +34,8 @@ class InvoiceObserver
->where('event_id', Webhook::EVENT_CREATE_INVOICE) ->where('event_id', Webhook::EVENT_CREATE_INVOICE)
->exists(); ->exists();
$invoice->load('client');
if ($subscriptions) { if ($subscriptions) {
WebhookHandler::dispatch(Webhook::EVENT_CREATE_INVOICE, $invoice, $invoice->company); WebhookHandler::dispatch(Webhook::EVENT_CREATE_INVOICE, $invoice, $invoice->company);
} }
@ -51,6 +53,9 @@ class InvoiceObserver
->where('event_id', Webhook::EVENT_UPDATE_INVOICE) ->where('event_id', Webhook::EVENT_UPDATE_INVOICE)
->exists(); ->exists();
$invoice->load('client');
if ($subscriptions) { if ($subscriptions) {
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company); WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company);
} }

View File

@ -30,6 +30,8 @@ class QuoteObserver
->where('event_id', Webhook::EVENT_CREATE_QUOTE) ->where('event_id', Webhook::EVENT_CREATE_QUOTE)
->exists(); ->exists();
$quote->load('client');
if ($subscriptions) { if ($subscriptions) {
WebhookHandler::dispatch(Webhook::EVENT_CREATE_QUOTE, $quote, $quote->company); WebhookHandler::dispatch(Webhook::EVENT_CREATE_QUOTE, $quote, $quote->company);
} }
@ -47,6 +49,9 @@ class QuoteObserver
->where('event_id', Webhook::EVENT_UPDATE_QUOTE) ->where('event_id', Webhook::EVENT_UPDATE_QUOTE)
->exists(); ->exists();
$quote->load('client');
if ($subscriptions) { if ($subscriptions) {
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_QUOTE, $quote, $quote->company); WebhookHandler::dispatch(Webhook::EVENT_UPDATE_QUOTE, $quote, $quote->company);
} }

View File

@ -56,7 +56,7 @@ class BasePaymentDriver
protected $refundable = false; protected $refundable = false;
/* Token billing */ /* Token billing */
protected $token_billing = false; public $token_billing = false;
/* Authorise payment methods */ /* Authorise payment methods */
protected $can_authorise_credit_card = false; protected $can_authorise_credit_card = false;

View File

@ -124,11 +124,29 @@ class CreditCard
public function paymentView($data) public function paymentView($data)
{ {
$data['gateway'] = $this->square_driver; $data['gateway'] = $this->square_driver;
$data['amount'] = $this->square_driver->payment_hash->data->amount_with_fee;
$data['currencyCode'] = $this->square_driver->client->getCurrencyCode();
$data['square_contact'] = $this->buildClientObject();
return render('gateways.square.credit_card.pay', $data); return render('gateways.square.credit_card.pay', $data);
} }
private function buildClientObject()
{
$client = new \stdClass;
$client->addressLines = [ $this->square_driver->client->address1 ?: '', $this->square_driver->client->address2 ?: ''];
$client->givenName = $this->square_driver->client->present()->first_name();
$client->familyName = $this->square_driver->client->present()->last_name();
$client->email = $this->square_driver->client->present()->email;
$client->phone = $this->square_driver->client->phone;
$client->city = $this->square_driver->client->city;
$client->region = $this->square_driver->client->state;
$client->country = $this->square_driver->client->country->iso_3166_2;
return (array)$client;
}
public function paymentResponse(PaymentResponseRequest $request) public function paymentResponse(PaymentResponseRequest $request)
{ {
$token = $request->sourceId; $token = $request->sourceId;
@ -152,6 +170,9 @@ class CreditCard
$body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId'));
$body->setReferenceId(Str::random(16)); $body->setReferenceId(Str::random(16));
if($request->has('verificationToken') && $request->input('verificationToken'))
$body->setVerificationToken($request->input('verificationToken'));
if ($request->shouldUseToken()) { if ($request->shouldUseToken()) {
$body->setCustomerId($cgt->gateway_customer_reference); $body->setCustomerId($cgt->gateway_customer_reference);
} }
@ -174,10 +195,19 @@ class CreditCard
{ {
$payment = \json_decode($response->getBody()); $payment = \json_decode($response->getBody());
$billing_address = new \Square\Models\Address();
$billing_address->setAddressLine1($this->square_driver->client->address1);
$billing_address->setAddressLine2($this->square_driver->client->address2);
$billing_address->setLocality($this->square_driver->client->city);
$billing_address->setAdministrativeDistrictLevel1($this->square_driver->client->state);
$billing_address->setPostalCode($this->square_driver->client->postal_code);
$billing_address->setCountry($this->square_driver->client->country->iso_3166_2);
$card = new \Square\Models\Card(); $card = new \Square\Models\Card();
$card->setCardholderName($this->square_driver->client->present()->name()); $card->setCardholderName($this->square_driver->client->present()->first_name(). " " .$this->square_driver->client->present()->last_name());
$card->setCustomerId($this->findOrCreateClient()); $card->setCustomerId($this->findOrCreateClient());
$card->setReferenceId(Str::random(8)); $card->setReferenceId(Str::random(8));
$card->setBillingAddress($billing_address);
$body = new \Square\Models\CreateCardRequest(Str::random(32), $payment->payment->id, $card); $body = new \Square\Models\CreateCardRequest(Str::random(32), $payment->payment->id, $card);
@ -270,11 +300,16 @@ class CreditCard
if ($api_response->isSuccess()) { if ($api_response->isSuccess()) {
$customers = $api_response->getBody(); $customers = $api_response->getBody();
$customers = json_decode($customers); $customers = json_decode($customers);
if(count(array($api_response->getBody(),1)) == 0)
$customers = false;
} else { } else {
$errors = $api_response->getErrors(); $errors = $api_response->getErrors();
} }
if ($customers) {
if (property_exists($customers, 'customers')) {
return $customers->customers[0]->id; return $customers->customers[0]->id;
} }

View File

@ -60,12 +60,16 @@ class ACH
'method' => '1', 'method' => '1',
*/ */
$response = $this->wepay_payment_driver->wepay->request('payment_bank/persist', [ try{
'client_id' => config('ninja.wepay.client_id'), $response = $this->wepay_payment_driver->wepay->request('payment_bank/persist', [
'client_secret' => config('ninja.wepay.client_secret'), 'client_id' => config('ninja.wepay.client_id'),
'payment_bank_id' => (int)$data['bank_account_id'], 'client_secret' => config('ninja.wepay.client_secret'),
]); 'payment_bank_id' => (int)$data['bank_account_id'],
]);
}
catch(\Exception $e){
throw new PaymentFailed($e->getMessage(), 400);
}
// display the response // display the response
// nlog($response); // nlog($response);
@ -202,26 +206,31 @@ class ACH
$app_fee = (config('ninja.wepay.fee_ach_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed'); $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( try{
// '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]), $response = $this->wepay_payment_driver->wepay->request('checkout/create', array(
'unique_id' => Str::random(40), // '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]),
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'), 'unique_id' => Str::random(40),
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee, 'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(), 'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
'short_description' => 'Goods and Services', 'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
'type' => 'goods', 'short_description' => 'Goods and Services',
'fee' => [ 'type' => 'goods',
'fee_payer' => config('ninja.wepay.fee_payer'), 'fee' => [
'app_fee' => $app_fee, 'fee_payer' => config('ninja.wepay.fee_payer'),
], 'app_fee' => $app_fee,
'payment_method' => array( ],
'type' => 'payment_bank', 'payment_method' => array(
'payment_bank' => array( 'type' => 'payment_bank',
'id' => $token->token 'payment_bank' => array(
'id' => $token->token
)
) )
) ));
)); }
catch(\Exception $e){
throw new PaymentFailed($e->getMessage(), 500);
}
/* Merge all data and store in the payment hash*/ /* Merge all data and store in the payment hash*/
$state = [ $state = [
'server_response' => $response, 'server_response' => $response,

View File

@ -84,7 +84,7 @@ class AutoBillInvoice extends AbstractService
$gateway_token = $this->getGateway($amount); $gateway_token = $this->getGateway($amount);
/* Bail out if no payment methods available */ /* Bail out if no payment methods available */
if (! $gateway_token || ! $gateway_token->gateway->driver($this->client)->token_billing){ if (! $gateway_token || ! $gateway_token->gateway || ! $gateway_token->gateway->driver($this->client)->token_billing){
nlog("Bailing out - no suitable gateway token found."); nlog("Bailing out - no suitable gateway token found.");
return $this->invoice; return $this->invoice;
} }
@ -291,23 +291,13 @@ class AutoBillInvoice extends AbstractService
* @param float $amount The amount to charge * @param float $amount The amount to charge
* @return ClientGatewayToken The client gateway token * @return ClientGatewayToken The client gateway token
*/ */
// private function
// {
// $gateway_tokens = $this->client->gateway_tokens()->orderBy('is_default', 'DESC')->get();
// foreach ($gateway_tokens as $gateway_token) {
// if ($this->validGatewayLimits($gateway_token, $amount)) {
// return $gateway_token;
// }
// }
// }
public function getGateway($amount) public function getGateway($amount)
{ {
//get all client gateway tokens and set the is_default one to the first record //get all client gateway tokens and set the is_default one to the first record
//$gateway_tokens = $this->client->gateway_tokens()->orderBy('is_default', 'DESC'); $gateway_tokens = $this->client->gateway_tokens()->orderBy('is_default', 'DESC');
$gateway_tokens = $this->client->gateway_tokens; // $gateway_tokens = $this->client->gateway_tokens;
$filtered_gateways = $gateway_tokens->filter(function ($gateway_token) use($amount) { $filtered_gateways = $gateway_tokens->filter(function ($gateway_token) use($amount) {

View File

@ -242,7 +242,8 @@ class RefundPayment
$invoice->service()->updateBalance($refunded_invoice['amount'])->save(); $invoice->service()->updateBalance($refunded_invoice['amount'])->save();
$invoice->ledger()->updateInvoiceBalance($refunded_invoice['amount'], "Refund of payment # {$this->payment->number}")->save(); $invoice->ledger()->updateInvoiceBalance($refunded_invoice['amount'], "Refund of payment # {$this->payment->number}")->save();
$invoice->paid_to_date -= $refunded_invoice['amount'];
if ($invoice->amount == $invoice->balance) { if ($invoice->amount == $invoice->balance) {
$invoice->service()->setStatus(Invoice::STATUS_SENT); $invoice->service()->setStatus(Invoice::STATUS_SENT);
} else { } else {

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.3.18', 'app_version' => '5.3.19',
'app_tag' => '5.3.18', 'app_tag' => '5.3.19',
'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', ''),

View File

@ -4,7 +4,7 @@ const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache'; const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = { const RESOURCES = {
"favicon.png": "dca91c54388f52eded692718d5a98b8b", "favicon.png": "dca91c54388f52eded692718d5a98b8b",
"/": "046e0fcccb3cc2bf087c3ca7100aab14", "/": "0d9690c2b925c794b94e0778817e5c19",
"assets/NOTICES": "9eb7e2eb2888ea5bae5f536720db37cd", "assets/NOTICES": "9eb7e2eb2888ea5bae5f536720db37cd",
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219", "assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024", "assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
@ -34,7 +34,7 @@ const RESOURCES = {
"favicon.ico": "51636d3a390451561744c42188ccd628", "favicon.ico": "51636d3a390451561744c42188ccd628",
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35", "icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed", "icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"main.dart.js": "cad794ed29ee69bb27294af52f923f50" "main.dart.js": "9542568225b6ad9e1ffbc87c3e6f74a2"
}; };
// The application shell files that are downloaded before a service worker can // The application shell files that are downloaded before a service worker can

File diff suppressed because one or more lines are too long

135292
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

137962
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

136995
public/main.last.dart.js vendored

File diff suppressed because one or more lines are too long

150000
public/main.next.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

134306
public/main.wasm.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@
"/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=08ea84e9451abd434cff", "/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=08ea84e9451abd434cff",
"/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=73b66e88e2daabcd6549", "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=73b66e88e2daabcd6549",
"/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c2b5f7831e1a46dd5fb2", "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c2b5f7831e1a46dd5fb2",
"/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=994c79534ee0a7391f69", "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=070c86b293b532c5a56c",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7", "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17", "/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344", "/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344",

View File

@ -43,12 +43,62 @@ class SquareCreditCard {
} }
} }
// ,
// function(err,verification) {
// if (err == null) {
// console.log("no error");
// console.log(verification);
// verificationToken = verificationResults.token;
// }
// console.log(err);
// die("verify buyer");
// }
async completePaymentWithoutToken(e) { async completePaymentWithoutToken(e) {
document.getElementById('errors').hidden = true; document.getElementById('errors').hidden = true;
e.target.parentElement.disabled = true; e.target.parentElement.disabled = true;
let result = await this.card.tokenize(); let result = await this.card.tokenize();
console.log("square token = " + result.token);
/* SCA */
let verificationToken;
try {
const verificationDetails = {
amount: document.querySelector('meta[name=amount]').content,
billingContact: JSON.parse(document.querySelector('meta[name=square_contact]').content),
currencyCode: document.querySelector('meta[name=currencyCode]').content,
intent: 'CHARGE'
};
console.log(verificationDetails);
const verificationResults = await this.payments.verifyBuyer(
result.token,
verificationDetails
);
verificationToken = verificationResults.token;
}
catch(typeError){
console.log(typeError);
die("failed in the catch");
}
// console.log(" verification tokem = " + verificationToken.token);
// verificationToken = verificationResults.token;
console.debug('Verification Token:', verificationToken);
document.querySelector('input[name="verificationToken"]').value =
verificationToken;
if (result.status === 'OK') { if (result.status === 'OK') {
document.getElementById('sourceId').value = result.token; document.getElementById('sourceId').value = result.token;
@ -77,6 +127,28 @@ class SquareCreditCard {
return document.getElementById('server_response').submit(); return document.getElementById('server_response').submit();
} }
/* SCA */
async verifyBuyer(token) {
console.log("in verify buyer");
const verificationDetails = {
amount: document.querySelector('meta[name=amount]').content,
billingContact: document.querySelector('meta[name=square_contact]').content,
currencyCode: document.querySelector('meta[name=currencyCode]').content,
intent: 'CHARGE'
};
const verificationResults = await this.payments.verifyBuyer(
token,
verificationDetails
);
console.log(" verification toke = " + verificationResults.token);
return verificationResults.token;
}
async handle() { async handle() {
await this.init(); await this.init();

View File

@ -0,0 +1,5 @@
@extends('portal.ninja2020.layout.error')
@section('title', __('Server Error'))
@section('code', '500')
@section('message', __($message) ?: 'System Error')

View File

@ -4,6 +4,10 @@
@section('gateway_head') @section('gateway_head')
<meta name="square-appId" content="{{ $gateway->company_gateway->getConfigField('applicationId') }}"> <meta name="square-appId" content="{{ $gateway->company_gateway->getConfigField('applicationId') }}">
<meta name="square-locationId" content="{{ $gateway->company_gateway->getConfigField('locationId') }}"> <meta name="square-locationId" content="{{ $gateway->company_gateway->getConfigField('locationId') }}">
<meta name="square_contact" content="{{ json_encode($square_contact) }}">
<meta name="amount" content="{{ $amount }}">
<meta name="currencyCode" content="{{ $currencyCode }}">
@endsection @endsection
@section('gateway_content') @section('gateway_content')
@ -17,6 +21,7 @@
<input type="hidden" name="token"> <input type="hidden" name="token">
<input type="hidden" name="sourceId" id="sourceId"> <input type="hidden" name="sourceId" id="sourceId">
<input type="hidden" name="verificationToken" id="verificationToken">
</form> </form>
<div class="alert alert-failure mb-4" hidden id="errors"></div> <div class="alert alert-failure mb-4" hidden id="errors"></div>