diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index b47ba9646177..797af5a09e86 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -16,6 +16,9 @@ use App\Events\Invoice\InvoiceWasViewed; use App\Events\Misc\InvitationWasViewed; use App\Events\Quote\QuoteWasViewed; use App\Http\Controllers\Controller; +use App\Models\Client; +use App\Models\ClientContact; +use App\Models\Payment; use App\Utils\Ninja; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; @@ -113,4 +116,18 @@ class InvitationController extends Controller public function routerForIframe(string $entity, string $client_hash, string $invitation_key) { } + + public function paymentRouter(string $contact_key, string $payment_id) + { + $contact = ClientContact::where('contact_key', $contact_key)->firstOrFail(); + $payment = Payment::find($this->decodePrimaryKey($payment_id)); + + if($payment->client_id != $contact->client_id) + abort(403, 'You are not authorized to view this resource'); + + auth()->guard('contact')->login($contact, true); + + return redirect()->route('client.payments.show', $payment->hashed_id); + + } } diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index e77fa41e7e74..25800eae7ecd 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -69,9 +69,13 @@ class CompanyController extends BaseController */ public function __construct(CompanyRepository $company_repo) { + parent::__construct(); $this->company_repo = $company_repo; + + $this->middleware('password_protected')->only(['destroy']); + } /** @@ -477,7 +481,7 @@ class CompanyController extends BaseController */ public function destroy(DestroyCompanyRequest $request, Company $company) { - + if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id) return response()->json(['message' => 'Cannot purge this company'], 400); diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index 14866f1e47b2..3e2806c7dd27 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -127,12 +127,11 @@ class EmailController extends BaseController $entity_obj->invitations->each(function ($invitation) use ($data, $entity_string, $entity_obj, $template) { - if ($invitation->contact->send_email && $invitation->contact->email) { + if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) { $entity_obj->service()->markSent()->save(); EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data); - // ->delay(now()->addSeconds(45)); } diff --git a/app/Http/Controllers/ImportJsonController.php b/app/Http/Controllers/ImportJsonController.php index bfdf1cfe29ed..f509dbc53e61 100644 --- a/app/Http/Controllers/ImportJsonController.php +++ b/app/Http/Controllers/ImportJsonController.php @@ -66,7 +66,8 @@ class ImportJsonController extends BaseController $file_location = $request->file('files') ->storeAs( 'migrations', - $request->file('files')->getClientOriginalName() + $request->file('files')->getClientOriginalName(), + config('filesystems.default'), ); if(Ninja::isHosted()) diff --git a/app/Http/Controllers/MigrationController.php b/app/Http/Controllers/MigrationController.php index 94ab88c5123d..dad349fde609 100644 --- a/app/Http/Controllers/MigrationController.php +++ b/app/Http/Controllers/MigrationController.php @@ -25,6 +25,7 @@ use App\Utils\Ninja; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Http\Request; use Illuminate\Support\Str; +use Illuminate\Support\Facades\App; class MigrationController extends BaseController { @@ -263,6 +264,10 @@ class MigrationController extends BaseController // Look for possible existing company (based on company keys). $existing_company = Company::whereRaw('BINARY `company_key` = ?', [$company->company_key])->first(); + App::forgetInstance('translator'); + $t = app('translator'); + $t->replace(Ninja::transformTranslations($user->account->companies()->first()->settings)); + if(!$existing_company && $company_count >=10) { $nmo = new NinjaMailerObject; diff --git a/app/Http/Controllers/PostMarkController.php b/app/Http/Controllers/PostMarkController.php index 89620945c98a..ceb9e1e4e0e9 100644 --- a/app/Http/Controllers/PostMarkController.php +++ b/app/Http/Controllers/PostMarkController.php @@ -213,7 +213,7 @@ class PostMarkController extends BaseController $request->input('MessageID') ); - LightLogs::create($bounce)->batch(); + LightLogs::create($spam)->batch(); SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company); } diff --git a/app/Http/Livewire/RecurringInvoices/UpdateAutoBilling.php b/app/Http/Livewire/RecurringInvoices/UpdateAutoBilling.php index 8f2144ad0561..dfcd3d771730 100644 --- a/app/Http/Livewire/RecurringInvoices/UpdateAutoBilling.php +++ b/app/Http/Livewire/RecurringInvoices/UpdateAutoBilling.php @@ -21,7 +21,7 @@ class UpdateAutoBilling extends Component public function updateAutoBilling(): void { - if ($this->invoice->auto_bill === 'optin' || $this->invoice->auto_bill === 'optout') { + if ($this->invoice->auto_bill == 'optin' || $this->invoice->auto_bill == 'optout') { $this->invoice->auto_bill_enabled = !$this->invoice->auto_bill_enabled; $this->invoice->save(); } diff --git a/app/Http/Middleware/PasswordProtection.php b/app/Http/Middleware/PasswordProtection.php index 54163fac473c..fdcbfd3dfdf1 100644 --- a/app/Http/Middleware/PasswordProtection.php +++ b/app/Http/Middleware/PasswordProtection.php @@ -52,7 +52,8 @@ class PasswordProtection $x_api_password = base64_decode($request->header('X-API-PASSWORD-BASE64')); } - if (Cache::get(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in')) { + // If no password supplied - then we just check if their authentication is in cache // + if (Cache::get(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in') && !$x_api_password) { Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout); diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index 7f2d1faad95d..4421cf5d69ed 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -101,8 +101,8 @@ class UpdateRecurringInvoiceRequest extends Request $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; } - if (isset($input['auto_bill'])) { - $input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']); + if (array_key_exists('auto_bill', $input) && isset($input['auto_bill']) && $this->setAutoBillFlag($input['auto_bill'])) { + $input['auto_bill_enabled'] = true; } if (array_key_exists('documents', $input)) { @@ -123,13 +123,8 @@ class UpdateRecurringInvoiceRequest extends Request */ private function setAutoBillFlag($auto_bill) :bool { - if ($auto_bill == 'always') { + if ($auto_bill == 'always') return true; - } - - // if($auto_bill == '') - // off / optin / optout will reset the status of this field to off to allow - // the client to choose whether to auto_bill or not. return false; } diff --git a/app/Import/Transformers/BaseTransformer.php b/app/Import/Transformers/BaseTransformer.php index 72a76edc0934..f0b2721c3e55 100644 --- a/app/Import/Transformers/BaseTransformer.php +++ b/app/Import/Transformers/BaseTransformer.php @@ -146,7 +146,7 @@ class BaseTransformer $number = 0; } - return Number::parseStringFloat($number); + return Number::parseFloat($number); } /** diff --git a/app/Import/Transformers/Csv/ExpenseTransformer.php b/app/Import/Transformers/Csv/ExpenseTransformer.php index d7aea550b8f8..baf5321058c8 100644 --- a/app/Import/Transformers/Csv/ExpenseTransformer.php +++ b/app/Import/Transformers/Csv/ExpenseTransformer.php @@ -25,7 +25,7 @@ class ExpenseTransformer extends BaseTransformer { 'date' => isset( $data['expense.date'] ) ? date( 'Y-m-d', strtotime( $data['expense.date'] ) ) : null, 'public_notes' => $this->getString( $data, 'expense.public_notes' ), 'private_notes' => $this->getString( $data, 'expense.private_notes' ), - 'expense_category_id' => isset( $data['expense.category'] ) ? $this->getExpenseCategoryId( $data['expense.category'] ) : null, + 'category_id' => isset( $data['expense.category'] ) ? $this->getExpenseCategoryId( $data['expense.category'] ) : null, 'project_id' => isset( $data['expense.project'] ) ? $this->getProjectId( $data['expense.project'] ) : null, 'payment_type_id' => isset( $data['expense.payment_type'] ) ? $this->getPaymentTypeId( $data['expense.payment_type'] ) : null, 'payment_date' => isset( $data['expense.payment_date'] ) ? date( 'Y-m-d', strtotime( $data['expense.payment_date'] ) ) : null, diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index 239104c4102d..64529b4d1045 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -498,6 +498,7 @@ class CompanyExport implements ShouldQueue if(Ninja::isHosted()) { Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path)); + unlink($zip_path); } App::forgetInstance('translator'); diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 591c03bf33f5..17017722a2ab 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -224,7 +224,7 @@ class CompanyImport implements ShouldQueue // if(mime_content_type(Storage::path($this->file_location)) == 'text/plain') // return Storage::path($this->file_location); - $path = TempFile::filePath(Storage::get($this->file_location), basename($this->file_location)); + $path = TempFile::filePath(Storage::disk(config('filesystems.default'))->get($this->file_location), basename($this->file_location)); $zip = new ZipArchive(); $archive = $zip->open($path); @@ -235,7 +235,7 @@ class CompanyImport implements ShouldQueue $zip->close(); $file_location = "{$file_path}/backup.json"; - if (! file_exists($file_location)) + if (! file_exists($file_path)) throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.'); return $file_location; @@ -568,7 +568,7 @@ class CompanyImport implements ShouldQueue { $this->genericImport(GroupSetting::class, - ['user_id', 'company_id', 'id', 'hashed_id',], + ['user_id', 'company_id', 'id', 'hashed_id'], [['users' => 'user_id']], 'group_settings', 'name'); @@ -580,7 +580,7 @@ class CompanyImport implements ShouldQueue { $this->genericImport(Subscription::class, - ['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id',], + ['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id'], [['group_settings' => 'group_id'], ['users' => 'user_id'], ['users' => 'assigned_user_id']], 'subscriptions', 'name'); diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 0c3fd1928772..057045531e71 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -224,7 +224,7 @@ class NinjaMailerJob implements ShouldQueue return true; /* On the hosted platform we set default contacts a @example.com email address - we shouldn't send emails to these types of addresses */ - if(Ninja::isHosted() && strpos($this->nmo->to_user->email, '@example.com') !== false) + if(Ninja::isHosted() && $this->nmo->to_user && strpos($this->nmo->to_user->email, '@example.com') !== false) return true; /* GMail users are uncapped */ diff --git a/app/Jobs/Quote/QuoteWorkflowSettings.php b/app/Jobs/Quote/QuoteWorkflowSettings.php index ffc5730ac0a6..54ec01ca03ea 100644 --- a/app/Jobs/Quote/QuoteWorkflowSettings.php +++ b/app/Jobs/Quote/QuoteWorkflowSettings.php @@ -55,8 +55,8 @@ class QuoteWorkflowSettings implements ShouldQueue }); } - if ($this->client->getSetting('auto_archive_quote')) { - $this->base_repository->archive($this->quote); - } + // if ($this->client->getSetting('auto_archive_quote')) { + // $this->base_repository->archive($this->quote); + // } } } diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index 63ce5c2e030f..d19ebd3847f3 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -116,7 +116,7 @@ class SendRecurring implements ShouldQueue nlog("Invoice {$invoice->number} created"); $invoice->invitations->each(function ($invitation) use ($invoice) { - if ($invitation->contact && strlen($invitation->contact->email) >=1 && $invoice->client->getSetting('auto_email_invoice')) { + if ($invitation->contact && !$invitation->contact->trashed() && strlen($invitation->contact->email) >=1 && $invoice->client->getSetting('auto_email_invoice')) { try{ EmailEntity::dispatch($invitation, $invoice->company); diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index 664c09afa9b1..4ee2ab0d4e19 100644 --- a/app/Jobs/Util/Import.php +++ b/app/Jobs/Util/Import.php @@ -262,8 +262,6 @@ class Import implements ShouldQueue /*After a migration first some basic jobs to ensure the system is up to date*/ VersionCheck::dispatch(); - - // CreateCompanyPaymentTerms::dispatchNow($sp035a66, $spaa9f78); // CreateCompanyTaskStatuses::dispatchNow($this->company, $this->user); diff --git a/app/Jobs/Util/SendFailedEmails.php b/app/Jobs/Util/SendFailedEmails.php index bffe66caa6eb..ae8f11be1d37 100644 --- a/app/Jobs/Util/SendFailedEmails.php +++ b/app/Jobs/Util/SendFailedEmails.php @@ -63,7 +63,7 @@ class SendFailedEmails implements ShouldQueue $invitation = $job_meta_array['entity_name']::where('key', $job_meta_array['invitation_key'])->with('contact')->first(); if ($invitation->invoice) { - if ($invitation->contact->send_email && $invitation->contact->email) { + if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) { EmailEntity::dispatch($invitation, $invitation->company, $job_meta_array['reminder_template']); } } diff --git a/app/Jobs/Util/StartMigration.php b/app/Jobs/Util/StartMigration.php index b4e54a07dc1c..b7d8654b0954 100644 --- a/app/Jobs/Util/StartMigration.php +++ b/app/Jobs/Util/StartMigration.php @@ -29,6 +29,7 @@ use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Storage; use ZipArchive; +use Illuminate\Support\Facades\App; class StartMigration implements ShouldQueue { @@ -122,6 +123,10 @@ class StartMigration implements ShouldQueue $this->company->update_products = $update_product_flag; $this->company->save(); + App::forgetInstance('translator'); + $t = app('translator'); + $t->replace(Ninja::transformTranslations($this->company->settings)); + } catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing | \Exception $e) { $this->company->update_products = $update_product_flag; diff --git a/app/Jobs/Util/SystemLogger.php b/app/Jobs/Util/SystemLogger.php index f4677cb2c72f..3775eda3faf6 100644 --- a/app/Jobs/Util/SystemLogger.php +++ b/app/Jobs/Util/SystemLogger.php @@ -55,6 +55,10 @@ class SystemLogger implements ShouldQueue MultiDB::setDb($this->company->db); $client_id = $this->client ? $this->client->id : null; + + if(!$this->client && !$this->company->owner()) + return; + $user_id = $this->client ? $this->client->user_id : $this->company->owner()->id; $sl = [ diff --git a/app/Listeners/SendVerificationNotification.php b/app/Listeners/SendVerificationNotification.php index 99e095b10d60..cec4f916b0cc 100644 --- a/app/Listeners/SendVerificationNotification.php +++ b/app/Listeners/SendVerificationNotification.php @@ -24,6 +24,7 @@ use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\App; class SendVerificationNotification implements ShouldQueue { @@ -53,6 +54,10 @@ class SendVerificationNotification implements ShouldQueue $event->user->service()->invite($event->company); + App::forgetInstance('translator'); + $t = app('translator'); + $t->replace(Ninja::transformTranslations($event->company->settings)); + $nmo = new NinjaMailerObject; $nmo->mailable = new UserAdded($event->company, $event->creating_user, $event->user); $nmo->company = $event->company; diff --git a/app/Mail/Engine/InvoiceEmailEngine.php b/app/Mail/Engine/InvoiceEmailEngine.php index d6cc1c82453b..9ede43715dc8 100644 --- a/app/Mail/Engine/InvoiceEmailEngine.php +++ b/app/Mail/Engine/InvoiceEmailEngine.php @@ -132,7 +132,6 @@ class InvoiceEmailEngine extends BaseEmailEngine } - } return $this; diff --git a/app/Mail/SupportMessageSent.php b/app/Mail/SupportMessageSent.php index 8192696600bb..337495ab8e99 100644 --- a/app/Mail/SupportMessageSent.php +++ b/app/Mail/SupportMessageSent.php @@ -45,8 +45,8 @@ class SupportMessageSent extends Mailable $log_file->seek(PHP_INT_MAX); $last_line = $log_file->key(); + $lines = new LimitIterator($log_file, $last_line - 100, $last_line); - $log_lines = iterator_to_array($lines); } @@ -76,6 +76,7 @@ class SupportMessageSent extends Mailable 'system_info' => $system_info, 'laravel_log' => $log_lines, 'logo' => $company->present()->logo(), + 'settings' => $company->settings ]); } } diff --git a/app/Mail/TemplateEmail.php b/app/Mail/TemplateEmail.php index 268424b4fb17..c14e6dcba23d 100644 --- a/app/Mail/TemplateEmail.php +++ b/app/Mail/TemplateEmail.php @@ -11,6 +11,8 @@ namespace App\Mail; +use App\Jobs\Invoice\CreateUbl; +use App\Models\Account; use App\Models\Client; use App\Models\ClientContact; use App\Models\User; @@ -116,6 +118,13 @@ class TemplateEmail extends Mailable } + if($this->invitation->invoice && $settings->ubl_email_attachment && $this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){ + + $ubl_string = CreateUbl::dispatchNow($this->invitation->invoice); + $this->attachData($ubl_string, $this->invitation->invoice->getFileName('xml')); + + } + return $this; } } diff --git a/app/Models/GroupSetting.php b/app/Models/GroupSetting.php index fd6bb52bf67a..5fe53afdac76 100644 --- a/app/Models/GroupSetting.php +++ b/app/Models/GroupSetting.php @@ -34,6 +34,15 @@ class GroupSetting extends StaticModel 'settings', ]; + protected $appends = [ + 'hashed_id', + ]; + + public function getHashedIdAttribute() + { + return $this->encodePrimaryKey($this->id); + } + protected $touches = []; public function company() diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index bff169918327..a6297b7034d0 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -84,10 +84,6 @@ class Invoice extends BaseModel 'custom_surcharge2', 'custom_surcharge3', 'custom_surcharge4', - // 'custom_surcharge_tax1', - // 'custom_surcharge_tax2', - // 'custom_surcharge_tax3', - // 'custom_surcharge_tax4', 'design_id', 'assigned_user_id', 'exchange_rate', diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 8416c35e4335..0043a284cb77 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -287,8 +287,24 @@ class Payment extends BaseModel event(new PaymentWasVoided($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); } - public function getLink() + // public function getLink() + // { + // return route('client.payments.show', $this->hashed_id); + // } + + public function getLink() :string { - return route('client.payments.show', $this->hashed_id); + + if(Ninja::isHosted()){ + $domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain(); + } + else + $domain = config('ninja.app_url'); + + return $domain.'/client/payment/'. $this->client->contacts()->first()->contact_key .'/' .$this->hashed_id; + + + } + } diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index b984b2b41eaa..219c851c9c37 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -389,6 +389,7 @@ class BaseDriver extends AbstractPaymentDriver $invoices->each(function ($invoice) { + if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) { $invoice->service()->deletePdf(); }); @@ -455,7 +456,7 @@ class BaseDriver extends AbstractPaymentDriver $invoices->first()->invitations->each(function ($invitation) use ($nmo){ - if ($invitation->contact->send_email && $invitation->contact->email) { + if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) { $nmo->to_user = $invitation->contact; NinjaMailerJob::dispatch($nmo); diff --git a/app/PaymentDrivers/WePay/ACH.php b/app/PaymentDrivers/WePay/ACH.php index fcc463f0157f..6de6498c2ea0 100644 --- a/app/PaymentDrivers/WePay/ACH.php +++ b/app/PaymentDrivers/WePay/ACH.php @@ -271,4 +271,68 @@ class ACH $this->wepay_payment_driver->storeGatewayToken($data); } + + + public function tokenBilling($token, $payment_hash) + { + + $token_meta = $token->meta; + + if(!property_exists($token_meta, 'state') || $token_meta->state != "authorized") + return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]); + + $amount = array_sum(array_column($this->wepay_payment_driver->payment_hash->invoices(), 'amount')) + $this->wepay_payment_driver->payment_hash->fee_total; + + $app_fee = (config('ninja.wepay.fee_cc_multiplier') * $amount) + config('ninja.wepay.fee_fixed'); + + $response = $this->wepay_payment_driver->wepay->request('checkout/create', array( + 'unique_id' => Str::random(40), + 'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'), + 'amount' => $amount, + 'currency' => $this->wepay_payment_driver->client->getCurrencyCode(), + 'short_description' => 'Goods and Services', + 'type' => 'goods', + 'fee' => [ + 'fee_payer' => config('ninja.wepay.fee_payer'), + 'app_fee' => $app_fee, + ], + 'payment_method' => array( + 'type' => 'payment_bank', + 'payment_bank' => array( + 'id' => $token->token + ) + ) + )); + + /* Merge all data and store in the payment hash*/ + $state = [ + 'server_response' => $response, + 'payment_hash' => $this->wepay_payment_driver->payment_hash, + ]; + + $this->wepay_payment_driver->payment_hash->data = array_merge((array) $this->wepay_payment_driver->payment_hash->data, $state); + $this->wepay_payment_driver->payment_hash->save(); + + if(in_array($response->state, ['authorized', 'captured'])){ + //success + nlog("success"); + $payment_status = $response->state == 'authorized' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING; + + return $this->processSuccessfulPayment($response, $payment_status, GatewayType::BANK_TRANSFER, true); + } + + if(in_array($response->state, ['released', 'cancelled', 'failed', 'expired'])){ + //some type of failure + nlog("failure"); + + $payment_status = $response->state == 'cancelled' ? Payment::STATUS_CANCELLED : Payment::STATUS_FAILED; + + $this->processUnSuccessfulPayment($response, $payment_status); + } + + } + + + + } diff --git a/app/PaymentDrivers/WePay/CreditCard.php b/app/PaymentDrivers/WePay/CreditCard.php index 8e6a155ddc43..5cb3567976b0 100644 --- a/app/PaymentDrivers/WePay/CreditCard.php +++ b/app/PaymentDrivers/WePay/CreditCard.php @@ -261,7 +261,8 @@ https://developer.wepay.com/api/api-calls/checkout private function storePaymentMethod($response, $payment_method_id) { -nlog("storing card"); + nlog("storing card"); + $payment_meta = new \stdClass; $payment_meta->exp_month = (string) $response->expiration_month; $payment_meta->exp_year = (string) $response->expiration_year; @@ -281,5 +282,60 @@ nlog("storing card"); + public function tokenBilling($cgt, $payment_hash) + { + + $amount = array_sum(array_column($this->wepay_payment_driver->payment_hash->invoices(), 'amount')) + $this->wepay_payment_driver->payment_hash->fee_total; + + + $app_fee = (config('ninja.wepay.fee_cc_multiplier') * $amount) + config('ninja.wepay.fee_fixed'); + // charge the credit card + $response = $this->wepay_payment_driver->wepay->request('checkout/create', array( + 'unique_id' => Str::random(40), + 'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'), + 'amount' => $amount, + 'currency' => $this->wepay_payment_driver->client->getCurrencyCode(), + 'short_description' => 'Goods and services', + 'type' => 'goods', + 'fee' => [ + 'fee_payer' => config('ninja.wepay.fee_payer'), + 'app_fee' => $app_fee, + ], + 'payment_method' => array( + 'type' => 'credit_card', + 'credit_card' => array( + 'id' => $cgt->token + ) + ) + )); + + /* Merge all data and store in the payment hash*/ + $state = [ + 'server_response' => $response, + 'payment_hash' => $payment_hash, + ]; + + $this->wepay_payment_driver->payment_hash->data = array_merge((array) $this->wepay_payment_driver->payment_hash->data, $state); + $this->wepay_payment_driver->payment_hash->save(); + + + if(in_array($response->state, ['authorized', 'captured'])){ + //success + nlog("success"); + $payment_status = $response->state == 'authorized' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING; + + return $this->processSuccessfulPayment($response, $payment_status, GatewayType::CREDIT_CARD, true); + } + + if(in_array($response->state, ['released', 'cancelled', 'failed', 'expired'])){ + //some type of failure + nlog("failure"); + + $payment_status = $response->state == 'cancelled' ? Payment::STATUS_CANCELLED : Payment::STATUS_FAILED; + + $this->processUnSuccessfulPayment($response, $payment_status); + } + } + } diff --git a/app/PaymentDrivers/WePay/WePayCommon.php b/app/PaymentDrivers/WePay/WePayCommon.php index ec28874bd0eb..2cf843b43828 100644 --- a/app/PaymentDrivers/WePay/WePayCommon.php +++ b/app/PaymentDrivers/WePay/WePayCommon.php @@ -22,7 +22,7 @@ trait WePayCommon { - private function processSuccessfulPayment($response, $payment_status, $gateway_type) + private function processSuccessfulPayment($response, $payment_status, $gateway_type, $return_payment = false) { if($gateway_type == GatewayType::BANK_TRANSFER) @@ -48,6 +48,9 @@ trait WePayCommon $this->wepay_payment_driver->client->company, ); + if($return_payment) + return $payment; + return redirect()->route('client.payments.show', ['payment' => $this->wepay_payment_driver->encodePrimaryKey($payment->id)]); } diff --git a/app/PaymentDrivers/WePayPaymentDriver.php b/app/PaymentDrivers/WePayPaymentDriver.php index 89f628654b3f..27c60e570b84 100644 --- a/app/PaymentDrivers/WePayPaymentDriver.php +++ b/app/PaymentDrivers/WePayPaymentDriver.php @@ -119,14 +119,14 @@ class WePayPaymentDriver extends BaseDriver $contact = $client->primary_contact()->first() ? $client->primary_contact()->first() : $lient->contacts->first(); $data['contact'] = $contact; - return $this->payment_method->authorizeView($data); //this is your custom implementation from here + return $this->payment_method->authorizeView($data); } public function authorizeResponse($request) { $this->init(); - return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here + return $this->payment_method->authorizeResponse($request); } public function verificationView(ClientGatewayToken $cgt) @@ -147,19 +147,23 @@ class WePayPaymentDriver extends BaseDriver { $this->init(); - return $this->payment_method->paymentView($data); //this is your custom implementation from here + return $this->payment_method->paymentView($data); } public function processPaymentResponse($request) { $this->init(); - return $this->payment_method->paymentResponse($request); //this is your custom implementation from here + return $this->payment_method->paymentResponse($request); } public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) { - return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here + $this->init(); + $this->setPaymentMethod($cgt->gateway_type_id); + $this->setPaymentHash($payment_hash); + + return $this->payment_method->tokenBilling($cgt, $payment_hash); } public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) diff --git a/app/Services/Credit/SendEmail.php b/app/Services/Credit/SendEmail.php index 3915b7503502..55599323f139 100644 --- a/app/Services/Credit/SendEmail.php +++ b/app/Services/Credit/SendEmail.php @@ -44,7 +44,7 @@ class SendEmail } $this->credit->invitations->each(function ($invitation) { - if ($invitation->contact->send_email && $invitation->contact->email) { + if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) { $email_builder = (new CreditEmail())->build($invitation, $this->reminder_template); // EmailCredit::dispatchNow($email_builder, $invitation, $invitation->company); diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index f6df31475ca2..7023afd562ce 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -192,6 +192,8 @@ class InvoiceService public function handleCancellation() { + $this->removeUnpaidGatewayFees(); + $this->invoice = (new HandleCancellation($this->invoice))->run(); return $this; @@ -199,6 +201,8 @@ class InvoiceService public function markDeleted() { + $this->removeUnpaidGatewayFees(); + $this->invoice = (new MarkInvoiceDeleted($this->invoice))->run(); return $this; @@ -213,6 +217,8 @@ class InvoiceService public function reverseCancellation() { + $this->removeUnpaidGatewayFees(); + $this->invoice = (new HandleCancellation($this->invoice))->reverse(); return $this; @@ -278,11 +284,14 @@ class InvoiceService public function updateStatus() { - if ((int)$this->invoice->balance == 0) { + if($this->invoice->status_id == Invoice::STATUS_DRAFT) + return $this; + + // if ((int)$this->invoice->balance == 0) { - $this->setStatus(Invoice::STATUS_PAID)->workFlow(); - // InvoiceWorkflowSettings::dispatchNow($this->invoice); - } + // $this->setStatus(Invoice::STATUS_PAID)->workFlow(); + + // } if ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) { $this->setStatus(Invoice::STATUS_PARTIAL); diff --git a/app/Services/Invoice/MarkInvoiceDeleted.php b/app/Services/Invoice/MarkInvoiceDeleted.php index 95590695d82f..51b7c3ccc12c 100644 --- a/app/Services/Invoice/MarkInvoiceDeleted.php +++ b/app/Services/Invoice/MarkInvoiceDeleted.php @@ -26,6 +26,8 @@ class MarkInvoiceDeleted extends AbstractService private $total_payments = 0; + private $balance_adjustment = 0; + public function __construct(Invoice $invoice) { $this->invoice = $invoice; @@ -51,7 +53,7 @@ class MarkInvoiceDeleted extends AbstractService private function adjustLedger() { // $this->invoice->ledger()->updatePaymentBalance($this->adjustment_amount * -1, 'Invoice Deleted - reducing ledger balance'); //reduces the payment balance by payment totals - $this->invoice->ledger()->updatePaymentBalance($this->invoice->balance * -1, 'Invoice Deleted - reducing ledger balance'); //reduces the payment balance by payment totals + $this->invoice->ledger()->updatePaymentBalance($this->balance_adjustment * -1, 'Invoice Deleted - reducing ledger balance'); //reduces the payment balance by payment totals return $this; } @@ -65,7 +67,7 @@ class MarkInvoiceDeleted extends AbstractService private function adjustBalance() { - $this->invoice->client->service()->updateBalance($this->invoice->balance * -1)->save(); //reduces the client balance by the invoice amount. + $this->invoice->client->service()->updateBalance($this->balance_adjustment * -1)->save(); //reduces the client balance by the invoice amount. return $this; } @@ -122,11 +124,14 @@ class MarkInvoiceDeleted extends AbstractService } - $this->total_payments = $this->invoice->payments->sum('amount') - $this->invoice->payments->sum('refunded');; + $this->total_payments = $this->invoice->payments->sum('amount') - $this->invoice->payments->sum('refunded'); + + $this->balance_adjustment = $this->invoice->balance; + //$this->total_payments = $this->invoice->payments->sum('amount - refunded'); -nlog("adjustment amount = {$this->adjustment_amount}"); -nlog("total payments = {$this->total_payments}"); + // nlog("adjustment amount = {$this->adjustment_amount}"); + // nlog("total payments = {$this->total_payments}"); return $this; } diff --git a/app/Services/Invoice/SendEmail.php b/app/Services/Invoice/SendEmail.php index 54cd0f326623..f96b95cbaabc 100644 --- a/app/Services/Invoice/SendEmail.php +++ b/app/Services/Invoice/SendEmail.php @@ -44,7 +44,7 @@ class SendEmail extends AbstractService } $this->invoice->invitations->each(function ($invitation) { - if ($invitation->contact->send_email && $invitation->contact->email) { + if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) { EmailEntity::dispatchNow($invitation, $invitation->company, $this->reminder_template); } }); diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index a1d733311e1e..0d10b83fa745 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -81,8 +81,14 @@ class RefundPayment if ($response['success'] == false) { $this->payment->save(); - throw new PaymentRefundFailed($response['description']); + + if(array_key_exists('description', $response)) + throw new PaymentRefundFailed($response['description']); + else + throw new PaymentRefundFailed(); + } + } } else { $this->payment->refunded += $this->total_refund; diff --git a/app/Services/Quote/SendEmail.php b/app/Services/Quote/SendEmail.php index 91ff69586505..8fb1c30024a0 100644 --- a/app/Services/Quote/SendEmail.php +++ b/app/Services/Quote/SendEmail.php @@ -42,7 +42,7 @@ class SendEmail } $this->quote->invitations->each(function ($invitation) { - if ($invitation->contact->send_email && $invitation->contact->email) { + if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) { EmailEntity::dispatchNow($invitation, $invitation->company, $this->reminder_template); } }); diff --git a/app/Utils/Traits/Inviteable.php b/app/Utils/Traits/Inviteable.php index a65dbc7e73f8..91520a63804d 100644 --- a/app/Utils/Traits/Inviteable.php +++ b/app/Utils/Traits/Inviteable.php @@ -47,8 +47,9 @@ trait Inviteable { $entity_type = Str::snake(class_basename($this->entityType())); - if(Ninja::isHosted()) + if(Ninja::isHosted()){ $domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain(); + } else $domain = config('ninja.app_url'); diff --git a/composer.json b/composer.json index 77996d3fcc91..958fa7e31aec 100644 --- a/composer.json +++ b/composer.json @@ -145,4 +145,4 @@ }, "minimum-stability": "dev", "prefer-stable": true -} +} \ No newline at end of file diff --git a/database/schema/db-ninja-01-schema.dump b/database/schema/db-ninja-01-schema.dump index 7e27ead7077a..87ee5eaa830e 100644 --- a/database/schema/db-ninja-01-schema.dump +++ b/database/schema/db-ninja-01-schema.dump @@ -372,9 +372,12 @@ CREATE TABLE `companies` ( `expense_inclusive_taxes` tinyint(1) NOT NULL DEFAULT '0', `session_timeout` int(11) NOT NULL DEFAULT '0', `oauth_password_required` tinyint(1) NOT NULL DEFAULT '0', - `invoice_task_datelog` tinyint(1) NOT NULL DEFAULT '0', + `invoice_task_datelog` tinyint(1) NOT NULL DEFAULT '1', `default_password_timeout` int(11) NOT NULL DEFAULT '30', `show_task_end_date` tinyint(1) NOT NULL DEFAULT '0', + `markdown_enabled` tinyint(1) NOT NULL DEFAULT '1', + `use_comma_as_decimal_place` tinyint(1) NOT NULL DEFAULT '0', + `report_include_drafts` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `companies_company_key_unique` (`company_key`), KEY `companies_industry_id_foreign` (`industry_id`), @@ -1959,3 +1962,14 @@ INSERT INTO `migrations` VALUES (80,'2021_06_24_095942_payments_table_currency_n INSERT INTO `migrations` VALUES (81,'2021_06_24_115919_update_designs',2); INSERT INTO `migrations` VALUES (82,'2021_07_08_115919_update_designs',3); INSERT INTO `migrations` VALUES (83,'2021_07_10_085821_activate_payfast_payment_driver',3); +INSERT INTO `migrations` VALUES (84,'2021_07_19_074503_set_invoice_task_datelog_true_in_companies_table',4); +INSERT INTO `migrations` VALUES (85,'2021_07_20_095537_activate_paytrace_payment_driver',4); +INSERT INTO `migrations` VALUES (86,'2021_07_21_213344_change_english_languages_tables',4); +INSERT INTO `migrations` VALUES (87,'2021_07_21_234227_activate_eway_payment_driver',4); +INSERT INTO `migrations` VALUES (88,'2021_08_03_115024_activate_mollie_payment_driver',4); +INSERT INTO `migrations` VALUES (89,'2021_08_05_235942_add_zelle_payment_type',4); +INSERT INTO `migrations` VALUES (90,'2021_08_07_222435_add_markdown_enabled_column_to_companies_table',4); +INSERT INTO `migrations` VALUES (91,'2021_08_10_034407_add_more_languages',4); +INSERT INTO `migrations` VALUES (92,'2021_08_18_220124_use_comma_as_decimal_place_companies_table',4); +INSERT INTO `migrations` VALUES (93,'2021_08_24_115919_update_designs',4); +INSERT INTO `migrations` VALUES (94,'2021_08_25_093105_report_include_drafts_in_companies_table',5); diff --git a/resources/views/email/support/message.blade.php b/resources/views/email/support/message.blade.php index db71b1a42a42..7ecc3d7b0531 100644 --- a/resources/views/email/support/message.blade.php +++ b/resources/views/email/support/message.blade.php @@ -1,4 +1,4 @@ -@component('email.template.admin', ['logo' => $logo ?? 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png']) +@component('email.template.admin', ['settings' => $settings, 'logo' => $logo ?? 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png']) {{-- Body --}} {{ $support_message }} diff --git a/resources/views/portal/ninja2020/components/livewire/payment-methods-table.blade.php b/resources/views/portal/ninja2020/components/livewire/payment-methods-table.blade.php index c6216e448402..767ed490170d 100644 --- a/resources/views/portal/ninja2020/components/livewire/payment-methods-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/payment-methods-table.blade.php @@ -41,7 +41,7 @@ - + {{ ctrans('texts.payment_type_id') }} diff --git a/routes/api.php b/routes/api.php index 38ac51ac3863..0eceb6ef2aba 100644 --- a/routes/api.php +++ b/routes/api.php @@ -47,7 +47,9 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::post('companies/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected'); Route::post('companies/purge_save_settings/{company}', 'MigrationController@purgeCompanySaveSettings')->middleware('password_protected'); + Route::resource('companies', 'CompanyController'); // name = (companies. index / create / show / update / destroy / edit + Route::put('companies/{company}/upload', 'CompanyController@upload'); Route::get('company_ledger', 'CompanyLedgerController@index')->name('company_ledger.index'); diff --git a/routes/client.php b/routes/client.php index b95d5a95ded0..dc103e11fc9b 100644 --- a/routes/client.php +++ b/routes/client.php @@ -25,6 +25,8 @@ Route::get('client/key_login/{contact_key}', 'ClientPortal\ContactHashLoginContr Route::get('client/magic_link/{magic_link}', 'ClientPortal\ContactHashLoginController@magicLink')->name('client.contact_magic_link')->middleware(['domain_db','contact_key_login']); Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download')->middleware(['document_db']); Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('client.error'); +Route::get('client/payment/{contact_key}/{payment_id}', 'ClientPortal\InvitationController@paymentRouter')->middleware(['domain_db','contact_key_login']); + Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence','domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () { Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit @@ -95,6 +97,7 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie Route::get('credit/{invitation_key}/download_pdf', 'CreditController@downloadPdf')->name('credit.download_invitation_key'); Route::get('{entity}/{invitation_key}/download', 'ClientPortal\InvitationController@routerForDownload'); Route::get('{entity}/{client_hash}/{invitation_key}', 'ClientPortal\InvitationController@routerForIframe')->name('invoice.client_hash_and_invitation_key'); //should never need this + }); Route::get('phantom/{entity}/{invitation_key}', '\App\Utils\PhantomJS\Phantom@displayInvitation')->middleware(['invite_db', 'phantom_secret'])->name('phantom_view'); diff --git a/tests/Feature/CancelInvoiceTest.php b/tests/Feature/CancelInvoiceTest.php index a4d5a8104cf7..5bdaf24e6b2b 100644 --- a/tests/Feature/CancelInvoiceTest.php +++ b/tests/Feature/CancelInvoiceTest.php @@ -54,11 +54,11 @@ class CancelInvoiceTest extends TestCase $this->assertEquals(Invoice::STATUS_SENT, $this->invoice->status_id); - $this->invoice->service()->handleCancellation()->save(); + $this->invoice->fresh()->service()->handleCancellation()->save(); $this->assertEquals(0, $this->invoice->fresh()->balance); $this->assertEquals($this->client->fresh()->balance, ($client_balance - $invoice_balance)); $this->assertNotEquals($client_balance, $this->client->fresh()->balance); - $this->assertEquals(Invoice::STATUS_CANCELLED, $this->invoice->status_id); + $this->assertEquals(Invoice::STATUS_CANCELLED, $this->invoice->fresh()->status_id); } } diff --git a/tests/Feature/CompanyTest.php b/tests/Feature/CompanyTest.php index 538ef194ffce..af604cbb67ad 100644 --- a/tests/Feature/CompanyTest.php +++ b/tests/Feature/CompanyTest.php @@ -11,6 +11,7 @@ namespace Tests\Feature; use App\DataMapper\CompanySettings; +use App\Http\Middleware\PasswordProtection; use App\Models\Company; use App\Models\CompanyToken; use App\Utils\Traits\MakesHash; @@ -47,6 +48,8 @@ class CompanyTest extends TestCase public function testCompanyList() { + $this->withoutMiddleware(PasswordProtection::class); + $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, @@ -117,6 +120,7 @@ class CompanyTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', ])->delete('/api/v1/companies/'.$this->encodePrimaryKey($company->id)) ->assertStatus(200); } diff --git a/tests/Feature/RefundTest.php b/tests/Feature/RefundTest.php index 1e4af1b635f4..8c33246569bf 100644 --- a/tests/Feature/RefundTest.php +++ b/tests/Feature/RefundTest.php @@ -14,6 +14,7 @@ use App\Factory\ClientFactory; use App\Factory\CreditFactory; use App\Factory\InvoiceFactory; use App\Helpers\Invoice\InvoiceSum; +use App\Models\ClientContact; use App\Models\Invoice; use App\Models\Payment; use App\Utils\Traits\MakesHash; @@ -63,6 +64,14 @@ class RefundTest extends TestCase $client = ClientFactory::create($this->company->id, $this->user->id); $client->save(); + $contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + $this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id $this->invoice->client_id = $client->id; $this->invoice->status_id = Invoice::STATUS_SENT; @@ -138,6 +147,15 @@ class RefundTest extends TestCase $client = ClientFactory::create($this->company->id, $this->user->id); $client->save(); + $contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + + $this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id $this->invoice->client_id = $client->id; $this->invoice->status_id = Invoice::STATUS_SENT; @@ -227,6 +245,14 @@ class RefundTest extends TestCase $client = ClientFactory::create($this->company->id, $this->user->id); $client->save(); + $contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + $this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id $this->invoice->client_id = $client->id; $this->invoice->status_id = Invoice::STATUS_SENT; @@ -303,6 +329,15 @@ class RefundTest extends TestCase $client = ClientFactory::create($this->company->id, $this->user->id); $client->save(); + $contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + + $this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id $this->invoice->client_id = $client->id; $this->invoice->status_id = Invoice::STATUS_SENT; @@ -388,6 +423,15 @@ class RefundTest extends TestCase $client = ClientFactory::create($this->company->id, $this->user->id); $client->save(); + $contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + + $this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id $this->invoice->client_id = $client->id; $this->invoice->status_id = Invoice::STATUS_SENT; @@ -497,6 +541,15 @@ class RefundTest extends TestCase $client = ClientFactory::create($this->company->id, $this->user->id); $client->save(); + $contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + + $this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id $this->invoice->client_id = $client->id; $this->invoice->status_id = Invoice::STATUS_SENT; diff --git a/tests/Unit/NumberTest.php b/tests/Unit/NumberTest.php index 254ec236398b..fa12911a8760 100644 --- a/tests/Unit/NumberTest.php +++ b/tests/Unit/NumberTest.php @@ -41,6 +41,27 @@ class NumberTest extends TestCase $this->assertEquals(2.15, $rounded); } + //this method proved an error! removing this method from production + // public function testImportFloatConversion() + // { + + // $amount = '€7,99'; + + // $converted_amount = Number::parseStringFloat($amount); + + // $this->assertEquals(799, $converted_amount); + + // } + + public function testParsingStringCurrency() + { + $amount = '€7,99'; + + $converted_amount = Number::parseFloat($amount); + + $this->assertEquals(7.99, $converted_amount); + } + // public function testParsingFloats() // { // Currency::all()->each(function ($currency) {