mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 16:37:31 -04:00 
			
		
		
		
	Merge branch 'v5-develop' into v5-stable
This commit is contained in:
		
						commit
						01b27c8cae
					
				| @ -1 +1 @@ | ||||
| 5.3.1 | ||||
| 5.3.2 | ||||
							
								
								
									
										75
									
								
								app/DataProviders/USStates.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								app/DataProviders/USStates.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| <?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\DataProviders; | ||||
| 
 | ||||
| class USStates | ||||
| { | ||||
|     protected static array $states = [ | ||||
|         'AL' => 'Alabama', | ||||
|         'AK' => 'Alaska', | ||||
|         'AZ' => 'Arizona', | ||||
|         'AR' => 'Arkansas', | ||||
|         'CA' => 'California', | ||||
|         'CO' => 'Colorado', | ||||
|         'CT' => 'Connecticut', | ||||
|         'DE' => 'Delaware', | ||||
|         'DC' => 'District Of Columbia', | ||||
|         'FL' => 'Florida', | ||||
|         'GA' => 'Georgia', | ||||
|         'HI' => 'Hawaii', | ||||
|         'ID' => 'Idaho', | ||||
|         'IL' => 'Illinois', | ||||
|         'IN' => 'Indiana', | ||||
|         'IA' => 'Iowa', | ||||
|         'KS' => 'Kansas', | ||||
|         'KY' => 'Kentucky', | ||||
|         'LA' => 'Louisiana', | ||||
|         'ME' => 'Maine', | ||||
|         'MD' => 'Maryland', | ||||
|         'MA' => 'Massachusetts', | ||||
|         'MI' => 'Michigan', | ||||
|         'MN' => 'Minnesota', | ||||
|         'MS' => 'Mississippi', | ||||
|         'MO' => 'Missouri', | ||||
|         'MT' => 'Montana', | ||||
|         'NE' => 'Nebraska', | ||||
|         'NV' => 'Nevada', | ||||
|         'NH' => 'New Hampshire', | ||||
|         'NJ' => 'New Jersey', | ||||
|         'NM' => 'New Mexico', | ||||
|         'NY' => 'New York', | ||||
|         'NC' => 'North Carolina', | ||||
|         'ND' => 'North Dakota', | ||||
|         'OH' => 'Ohio', | ||||
|         'OK' => 'Oklahoma', | ||||
|         'OR' => 'Oregon', | ||||
|         'PA' => 'Pennsylvania', | ||||
|         'RI' => 'Rhode Island', | ||||
|         'SC' => 'South Carolina', | ||||
|         'SD' => 'South Dakota', | ||||
|         'TN' => 'Tennessee', | ||||
|         'TX' => 'Texas', | ||||
|         'UT' => 'Utah', | ||||
|         'VT' => 'Vermont', | ||||
|         'VA' => 'Virginia', | ||||
|         'WA' => 'Washington', | ||||
|         'WV' => 'West Virginia', | ||||
|         'WI' => 'Wisconsin', | ||||
|         'WY' => 'Wyoming', | ||||
|     ]; | ||||
| 
 | ||||
|     public static function get(): array | ||||
|     { | ||||
|         return self::$states; | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|          | ||||
|  | ||||
| @ -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));
 | ||||
|                  | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
|  | ||||
| @ -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(); | ||||
|         } | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
|  | ||||
| @ -146,7 +146,7 @@ class BaseTransformer | ||||
|             $number = 0; | ||||
|         } | ||||
| 
 | ||||
|         return Number::parseStringFloat($number); | ||||
|         return Number::parseFloat($number); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -114,9 +114,6 @@ class CreateAccount | ||||
| 
 | ||||
|         $spaa9f78->fresh(); | ||||
| 
 | ||||
|         //todo implement SLACK notifications
 | ||||
|         //$sp035a66->notification(new NewAccountCreated($spaa9f78, $sp035a66))->ninja();
 | ||||
| 
 | ||||
|         if(Ninja::isHosted()) | ||||
|             \Modules\Admin\Jobs\Account\NinjaUser::dispatch([], $sp035a66); | ||||
| 
 | ||||
|  | ||||
| @ -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'); | ||||
|  | ||||
| @ -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'); | ||||
|  | ||||
| @ -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 */ | ||||
|  | ||||
| @ -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);
 | ||||
|         // }
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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);
 | ||||
| 
 | ||||
|  | ||||
| @ -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']); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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 = [ | ||||
|  | ||||
| @ -17,13 +17,13 @@ use App\Jobs\Mail\NinjaMailerObject; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Mail\Admin\VerifyUserObject; | ||||
| use App\Mail\User\UserAdded; | ||||
| use App\Notifications\Ninja\VerifyUser; | ||||
| use App\Utils\Ninja; | ||||
| use Exception; | ||||
| 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 +53,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; | ||||
|  | ||||
| @ -132,7 +132,6 @@ class InvoiceEmailEngine extends BaseEmailEngine | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|  | ||||
| @ -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 | ||||
|                 ]); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -15,11 +15,13 @@ use App\Jobs\Mail\NinjaMailerJob; | ||||
| use App\Jobs\Mail\NinjaMailerObject; | ||||
| use App\Mail\Ninja\EmailQuotaExceeded; | ||||
| use App\Models\Presenters\AccountPresenter; | ||||
| use App\Notifications\Ninja\EmailQuotaNotification; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Carbon\Carbon; | ||||
| use DateTime; | ||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||
| use Illuminate\Support\Facades\App; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use Laracasts\Presenter\PresentableTrait; | ||||
| 
 | ||||
| @ -384,6 +386,10 @@ class Account extends BaseModel | ||||
| 
 | ||||
|                 if(is_null(Cache::get("throttle_notified:{$this->key}"))) { | ||||
| 
 | ||||
|                     App::forgetInstance('translator'); | ||||
|                     $t = app('translator'); | ||||
|                     $t->replace(Ninja::transformTranslations($this->companies()->first()->settings)); | ||||
| 
 | ||||
|                     $nmo = new NinjaMailerObject; | ||||
|                     $nmo->mailable = new EmailQuotaExceeded($this->companies()->first()); | ||||
|                     $nmo->company = $this->companies()->first(); | ||||
| @ -392,6 +398,9 @@ class Account extends BaseModel | ||||
|                     NinjaMailerJob::dispatch($nmo); | ||||
| 
 | ||||
|                     Cache::put("throttle_notified:{$this->key}", true, 60 * 24); | ||||
| 
 | ||||
|                     if(config('ninja.notification.slack')) | ||||
|                         $this->companies()->first()->notification(new EmailQuotaNotification($this))->ninja(); | ||||
|                 } | ||||
| 
 | ||||
|                 return true; | ||||
|  | ||||
| @ -83,7 +83,7 @@ class Gateway extends StaticModel | ||||
|                 break; | ||||
|             case 3: | ||||
|                 return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]];//eWay
 | ||||
|                 break;    | ||||
|                 break; | ||||
|             case 11: | ||||
|                 return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false]];//Payfast
 | ||||
|                 break; | ||||
| @ -106,11 +106,12 @@ class Gateway extends StaticModel | ||||
|             case 49: | ||||
|                 return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], | ||||
|                     GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true]]; //WePay
 | ||||
|                 break;     | ||||
|                 break; | ||||
|             case 50: | ||||
|                 return [ | ||||
|                     GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], //Braintree
 | ||||
|                     GatewayType::PAYPAL => ['refund' => true, 'token_billing' => true] | ||||
|                     GatewayType::PAYPAL => ['refund' => true, 'token_billing' => true], | ||||
|                     GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true], | ||||
|                 ]; | ||||
|                 break; | ||||
|             case 7: | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -48,7 +48,15 @@ class ClientPresenter extends EntityPresenter | ||||
| 
 | ||||
|     public function email() | ||||
|     { | ||||
|         return $this->entity->primary_contact->first() !== null ? $this->entity->primary_contact->first()->email : 'No Email Set'; | ||||
|         $primary_contact = $this->entity->primary_contact->first(); | ||||
| 
 | ||||
|         if($primary_contact && strlen($primary_contact->email) > 1) | ||||
|             return $primary_contact->email; | ||||
| 
 | ||||
|         $contact = $this->entity->contacts->whereNotNull('email')->first(); | ||||
| 
 | ||||
|         return $contact ? $contact->email : 'No Email Set'; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function address() | ||||
|  | ||||
							
								
								
									
										88
									
								
								app/Notifications/Ninja/EmailQuotaNotification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								app/Notifications/Ninja/EmailQuotaNotification.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | ||||
| <?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\Notifications\Ninja; | ||||
| 
 | ||||
| use Illuminate\Bus\Queueable; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Notifications\Messages\MailMessage; | ||||
| use Illuminate\Notifications\Messages\SlackMessage; | ||||
| use Illuminate\Notifications\Notification; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class EmailQuotaNotification extends Notification  | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * Create a new notification instance. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
| 
 | ||||
|     protected $account; | ||||
| 
 | ||||
|     public function __construct($account) | ||||
|     { | ||||
|         $this->account = $account; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the notification's delivery channels. | ||||
|      * | ||||
|      * @param  mixed  $notifiable | ||||
|      * @return array | ||||
|      */ | ||||
|     public function via($notifiable) | ||||
|     { | ||||
|         return ['slack']; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the mail representation of the notification. | ||||
|      * | ||||
|      * @param  mixed  $notifiable | ||||
|      * @return MailMessage | ||||
|      */ | ||||
|     public function toMail($notifiable) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the array representation of the notification. | ||||
|      * | ||||
|      * @param  mixed  $notifiable | ||||
|      * @return array | ||||
|      */ | ||||
|     public function toArray($notifiable) | ||||
|     { | ||||
|         return [ | ||||
|             //
 | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function toSlack($notifiable) | ||||
|     { | ||||
| 
 | ||||
|         $content = "Email quota exceeded by Account {$this->account->key} \n"; | ||||
| 
 | ||||
|         $owner = $this->account->companies()->first()->owner(); | ||||
| 
 | ||||
|         $content .= "Owner {$owner->present()->name() } | {$owner->email}"; | ||||
| 
 | ||||
|         return (new SlackMessage) | ||||
|                 ->success() | ||||
|                 ->from(ctrans('texts.notification_bot')) | ||||
|                 ->image('https://app.invoiceninja.com/favicon.png') | ||||
|                 ->content($content); | ||||
|     } | ||||
| } | ||||
| @ -360,16 +360,15 @@ class BaseDriver extends AbstractPaymentDriver | ||||
| 
 | ||||
|     public function processInternallyFailedPayment($gateway, $e) | ||||
|     { | ||||
| 
 | ||||
|         $this->unWindGatewayFees($this->payment_hash); | ||||
|         if (!is_null($this->payment_hash)) { | ||||
|             $this->unWindGatewayFees($this->payment_hash); | ||||
|         } | ||||
| 
 | ||||
|         if ($e instanceof CheckoutHttpException) { | ||||
|             $error = $e->getBody(); | ||||
|         } | ||||
|         else if ($e instanceof Exception) { | ||||
|         } else if ($e instanceof Exception) { | ||||
|             $error = $e->getMessage(); | ||||
|         } | ||||
|         else | ||||
|         } else | ||||
|             $error = $e->getMessage(); | ||||
| 
 | ||||
|         PaymentFailureMailer::dispatch( | ||||
| @ -379,29 +378,34 @@ class BaseDriver extends AbstractPaymentDriver | ||||
|             $this->payment_hash | ||||
|         ); | ||||
| 
 | ||||
|         $nmo = new NinjaMailerObject; | ||||
|         $nmo->mailable = new NinjaMailer( (new ClientPaymentFailureObject($gateway->client, $error, $gateway->client->company, $this->payment_hash))->build() ); | ||||
|         $nmo->company = $gateway->client->company; | ||||
|         $nmo->settings = $gateway->client->company->settings; | ||||
|         if (!is_null($this->payment_hash)) { | ||||
| 
 | ||||
|         $invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get(); | ||||
|             $nmo = new NinjaMailerObject; | ||||
|             $nmo->mailable = new NinjaMailer((new ClientPaymentFailureObject($gateway->client, $error, $gateway->client->company, $this->payment_hash))->build()); | ||||
|             $nmo->company = $gateway->client->company; | ||||
|             $nmo->settings = $gateway->client->company->settings; | ||||
| 
 | ||||
|         $invoices->each(function ($invoice){ | ||||
|             $invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get(); | ||||
| 
 | ||||
|             $invoice->service()->deletePdf(); | ||||
|             $invoices->each(function ($invoice) { | ||||
| 
 | ||||
|         }); | ||||
|                 if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email)  | ||||
|                     $invoice->service()->deletePdf(); | ||||
|                  | ||||
|             }); | ||||
| 
 | ||||
|         $invoices->first()->invitations->each(function ($invitation) use ($nmo){ | ||||
|             $invoices->first()->invitations->each(function ($invitation) use ($nmo) { | ||||
| 
 | ||||
|             if ($invitation->contact->send_email && $invitation->contact->email) { | ||||
|                 if ($invitation->contact->send_email && $invitation->contact->email) { | ||||
| 
 | ||||
|                 $nmo->to_user = $invitation->contact; | ||||
|                 NinjaMailerJob::dispatch($nmo); | ||||
|                     $nmo->to_user = $invitation->contact; | ||||
|                     NinjaMailerJob::dispatch($nmo); | ||||
|                 } | ||||
|              | ||||
|             }); | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|         }); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         SystemLogger::dispatch( | ||||
| @ -456,7 +460,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); | ||||
|  | ||||
							
								
								
									
										186
									
								
								app/PaymentDrivers/Braintree/ACH.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								app/PaymentDrivers/Braintree/ACH.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,186 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\PaymentDrivers\Braintree; | ||||
| 
 | ||||
| use App\Exceptions\PaymentFailed; | ||||
| use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; | ||||
| use App\Http\Requests\Request; | ||||
| use App\Jobs\Mail\PaymentFailureMailer; | ||||
| use App\Jobs\Util\SystemLogger; | ||||
| use App\Models\ClientGatewayToken; | ||||
| use App\Models\GatewayType; | ||||
| use App\Models\Payment; | ||||
| use App\Models\PaymentType; | ||||
| use App\Models\SystemLog; | ||||
| use App\PaymentDrivers\BraintreePaymentDriver; | ||||
| use App\PaymentDrivers\Common\MethodInterface; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| 
 | ||||
| class ACH implements MethodInterface | ||||
| { | ||||
|     use MakesHash; | ||||
| 
 | ||||
|     protected BraintreePaymentDriver $braintree; | ||||
| 
 | ||||
|     public function __construct(BraintreePaymentDriver $braintree) | ||||
|     { | ||||
|         $this->braintree = $braintree; | ||||
| 
 | ||||
|         $this->braintree->init(); | ||||
|     } | ||||
| 
 | ||||
|     public function authorizeView(array $data) | ||||
|     { | ||||
|         $data['gateway'] = $this->braintree; | ||||
|         $data['client_token'] = $this->braintree->gateway->clientToken()->generate(); | ||||
| 
 | ||||
|         return render('gateways.braintree.ach.authorize', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function authorizeResponse(Request $request) | ||||
|     { | ||||
|         $request->validate([ | ||||
|             'nonce' => ['required'], | ||||
|             'gateway_type_id' => ['required'], | ||||
|         ]); | ||||
| 
 | ||||
|         $customer = $this->braintree->findOrCreateCustomer(); | ||||
| 
 | ||||
|         $result = $this->braintree->gateway->paymentMethod()->create([ | ||||
|             'customerId' => $customer->id, | ||||
|             'paymentMethodNonce' => $request->nonce, | ||||
|             'options' => [ | ||||
|                 'usBankAccountVerificationMethod' => \Braintree\Result\UsBankAccountVerification::NETWORK_CHECK, | ||||
|             ], | ||||
|         ]); | ||||
| 
 | ||||
|         if ($result->success && optional($result->paymentMethod)->verified) { | ||||
|             $account = $result->paymentMethod; | ||||
| 
 | ||||
|             try { | ||||
|                 $payment_meta = new \stdClass; | ||||
|                 $payment_meta->brand = (string)$account->bankName; | ||||
|                 $payment_meta->last4 = (string)$account->last4; | ||||
|                 $payment_meta->type = GatewayType::BANK_TRANSFER; | ||||
|                 $payment_meta->state = 'authorized'; | ||||
| 
 | ||||
|                 $data = [ | ||||
|                     'payment_meta' => $payment_meta, | ||||
|                     'token' => $account->token, | ||||
|                     'payment_method_id' => $request->gateway_type_id, | ||||
|                 ]; | ||||
| 
 | ||||
|                 $this->braintree->storeGatewayToken($data, ['gateway_customer_reference' => $customer->id]); | ||||
| 
 | ||||
|                 return redirect()->route('client.payment_methods.index')->withMessage(ctrans('texts.payment_method_added')); | ||||
|             } catch (\Exception $e) { | ||||
|                 return $this->braintree->processInternallyFailedPayment($this->braintree, $e); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return back()->withMessage(ctrans('texts.unable_to_verify_payment_method')); | ||||
|     } | ||||
| 
 | ||||
|     public function paymentView(array $data) | ||||
|     { | ||||
|         $data['gateway'] = $this->braintree; | ||||
|         $data['currency'] = $this->braintree->client->getCurrencyCode(); | ||||
|         $data['payment_method_id'] = GatewayType::BANK_TRANSFER; | ||||
|         $data['amount'] = $this->braintree->payment_hash->data->amount_with_fee; | ||||
| 
 | ||||
|         return render('gateways.braintree.ach.pay', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function paymentResponse(PaymentResponseRequest $request) | ||||
|     { | ||||
|         $request->validate([ | ||||
|             'source' => ['required'], | ||||
|             'payment_hash' => ['required'], | ||||
|         ]); | ||||
| 
 | ||||
|         $customer = $this->braintree->findOrCreateCustomer(); | ||||
| 
 | ||||
|         $token = ClientGatewayToken::query() | ||||
|             ->where('client_id', auth('contact')->user()->client->id) | ||||
|             ->where('id', $this->decodePrimaryKey($request->source)) | ||||
|             ->firstOrFail(); | ||||
| 
 | ||||
|         $result = $this->braintree->gateway->transaction()->sale([ | ||||
|             'amount' => $this->braintree->payment_hash->data->amount_with_fee, | ||||
|             'paymentMethodToken' => $token->token, | ||||
|             'options' => [ | ||||
|                 'submitForSettlement' => true | ||||
|             ], | ||||
|         ]); | ||||
| 
 | ||||
|         if ($result->success) { | ||||
|             $this->braintree->logSuccessfulGatewayResponse(['response' => $request->server_response, 'data' => $this->braintree->payment_hash], SystemLog::TYPE_BRAINTREE); | ||||
| 
 | ||||
|             return $this->processSuccessfulPayment($result); | ||||
|         } | ||||
| 
 | ||||
|         return $this->processUnsuccessfulPayment($result); | ||||
|     } | ||||
| 
 | ||||
|     private function processSuccessfulPayment($response) | ||||
|     { | ||||
|         $state = $this->braintree->payment_hash->data; | ||||
| 
 | ||||
|         $data = [ | ||||
|             'payment_type' => PaymentType::ACH, | ||||
|             'amount' => $this->braintree->payment_hash->data->amount_with_fee, | ||||
|             'transaction_reference' => $response->transaction->id, | ||||
|             'gateway_type_id' => GatewayType::BANK_TRANSFER, | ||||
|         ]; | ||||
| 
 | ||||
|         $payment = $this->braintree->createPayment($data, Payment::STATUS_COMPLETED); | ||||
| 
 | ||||
|         SystemLogger::dispatch( | ||||
|             ['response' => $response, 'data' => $data], | ||||
|             SystemLog::CATEGORY_GATEWAY_RESPONSE, | ||||
|             SystemLog::EVENT_GATEWAY_SUCCESS, | ||||
|             SystemLog::TYPE_BRAINTREE, | ||||
|             $this->braintree->client, | ||||
|             $this->braintree->client->company, | ||||
|         ); | ||||
| 
 | ||||
|         return redirect()->route('client.payments.show', ['payment' => $this->braintree->encodePrimaryKey($payment->id)]); | ||||
|     } | ||||
| 
 | ||||
|     private function processUnsuccessfulPayment($response) | ||||
|     { | ||||
|         PaymentFailureMailer::dispatch($this->braintree->client, $response->transaction->additionalProcessorResponse, $this->braintree->client->company, $this->braintree->payment_hash->data->amount_with_fee); | ||||
| 
 | ||||
|         PaymentFailureMailer::dispatch( | ||||
|             $this->braintree->client, | ||||
|             $response, | ||||
|             $this->braintree->client->company, | ||||
|             $this->braintree->payment_hash->data->amount_with_fee, | ||||
|         ); | ||||
| 
 | ||||
|         $message = [ | ||||
|             'server_response' => $response, | ||||
|             'data' => $this->braintree->payment_hash->data, | ||||
|         ]; | ||||
| 
 | ||||
|         SystemLogger::dispatch( | ||||
|             $message, | ||||
|             SystemLog::CATEGORY_GATEWAY_RESPONSE, | ||||
|             SystemLog::EVENT_GATEWAY_FAILURE, | ||||
|             SystemLog::TYPE_BRAINTREE, | ||||
|             $this->braintree->client, | ||||
|             $this->braintree->client->company, | ||||
|         ); | ||||
| 
 | ||||
|         throw new PaymentFailed($response->transaction->additionalProcessorResponse, $response->transaction->processorResponseCode); | ||||
|     } | ||||
| } | ||||
| @ -23,6 +23,7 @@ use App\Models\Payment; | ||||
| use App\Models\PaymentHash; | ||||
| use App\Models\PaymentType; | ||||
| use App\Models\SystemLog; | ||||
| use App\PaymentDrivers\Braintree\ACH; | ||||
| use App\PaymentDrivers\Braintree\CreditCard; | ||||
| use App\PaymentDrivers\Braintree\PayPal; | ||||
| use Braintree\Gateway; | ||||
| @ -45,6 +46,7 @@ class BraintreePaymentDriver extends BaseDriver | ||||
|     public static $methods = [ | ||||
|         GatewayType::CREDIT_CARD => CreditCard::class, | ||||
|         GatewayType::PAYPAL => PayPal::class, | ||||
|         GatewayType::BANK_TRANSFER => ACH::class, | ||||
|     ]; | ||||
| 
 | ||||
|     const SYSTEM_LOG_TYPE = SystemLog::TYPE_BRAINTREE; | ||||
| @ -72,9 +74,10 @@ class BraintreePaymentDriver extends BaseDriver | ||||
|     { | ||||
|         $types = [ | ||||
|             GatewayType::PAYPAL, | ||||
|             GatewayType::CREDIT_CARD | ||||
|             GatewayType::CREDIT_CARD, | ||||
|             GatewayType::BANK_TRANSFER, | ||||
|         ]; | ||||
|          | ||||
| 
 | ||||
|         return $types; | ||||
|     } | ||||
| 
 | ||||
| @ -125,9 +128,9 @@ class BraintreePaymentDriver extends BaseDriver | ||||
|         $this->init(); | ||||
| 
 | ||||
|         try{ | ||||
|    | ||||
| 
 | ||||
|             $response = $this->gateway->transaction()->refund($payment->transaction_reference, $amount); | ||||
|    | ||||
| 
 | ||||
|         } catch (Exception $e) { | ||||
| 
 | ||||
|             $data = [ | ||||
| @ -137,12 +140,12 @@ class BraintreePaymentDriver extends BaseDriver | ||||
|                 'description' => $e->getMessage(), | ||||
|                 'code' => $e->getCode(), | ||||
|             ]; | ||||
|    | ||||
| 
 | ||||
|             SystemLogger::dispatch(['server_response' => null, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company); | ||||
| 
 | ||||
|             return $data; | ||||
|         } | ||||
|    | ||||
| 
 | ||||
|         if($response->success) | ||||
|         { | ||||
| 
 | ||||
| @ -218,7 +221,8 @@ class BraintreePaymentDriver extends BaseDriver | ||||
|                 SystemLog::CATEGORY_GATEWAY_RESPONSE, | ||||
|                 SystemLog::EVENT_GATEWAY_SUCCESS, | ||||
|                 SystemLog::TYPE_BRAINTREE, | ||||
|                 $this->client | ||||
|                 $this->client, | ||||
|                 $this->client->company, | ||||
|             ); | ||||
| 
 | ||||
|             return $payment; | ||||
| @ -239,7 +243,8 @@ class BraintreePaymentDriver extends BaseDriver | ||||
|                 SystemLog::CATEGORY_GATEWAY_RESPONSE, | ||||
|                 SystemLog::EVENT_GATEWAY_FAILURE, | ||||
|                 SystemLog::TYPE_BRAINTREE, | ||||
|                 $this->client | ||||
|                 $this->client, | ||||
|                 $this->client->company | ||||
|             ); | ||||
| 
 | ||||
|             return false; | ||||
|  | ||||
							
								
								
									
										46
									
								
								app/PaymentDrivers/Common/MethodInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/PaymentDrivers/Common/MethodInterface.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com). | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\PaymentDrivers\Common; | ||||
| 
 | ||||
| use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; | ||||
| use App\Http\Requests\Request; | ||||
| 
 | ||||
| interface MethodInterface | ||||
| { | ||||
|     /** | ||||
|      * Authorization page for the gateway method. | ||||
|      * | ||||
|      * @param array $data | ||||
|      */ | ||||
|     public function authorizeView(array $data); | ||||
| 
 | ||||
|     /** | ||||
|      * Process the response from the authorization page. | ||||
|      * | ||||
|      * @param Request $request | ||||
|      */ | ||||
|     public function authorizeResponse(Request $request); | ||||
| 
 | ||||
|     /** | ||||
|      * Payment page for the gateway method. | ||||
|      * | ||||
|      * @param array $data | ||||
|      */ | ||||
|     public function paymentView(array $data); | ||||
| 
 | ||||
|     /** | ||||
|      * Process the response from the payments page. | ||||
|      * | ||||
|      * @param PaymentResponseRequest $request | ||||
|      */ | ||||
|     public function paymentResponse(PaymentResponseRequest $request); | ||||
| } | ||||
| @ -315,20 +315,36 @@ class StripePaymentDriver extends BaseDriver | ||||
| 
 | ||||
|         $client_gateway_token = ClientGatewayToken::whereClientId($this->client->id)->whereCompanyGatewayId($this->company_gateway->id)->first(); | ||||
| 
 | ||||
|         //Search by customer reference
 | ||||
|         if ($client_gateway_token && $client_gateway_token->gateway_customer_reference) { | ||||
| 
 | ||||
|             $customer = Customer::retrieve($client_gateway_token->gateway_customer_reference, $this->stripe_connect_auth); | ||||
|         } else { | ||||
| 
 | ||||
|             $data['name'] = $this->client->present()->name(); | ||||
|             $data['phone'] = $this->client->present()->phone(); | ||||
|              | ||||
|             if (filter_var($this->client->present()->email(), FILTER_VALIDATE_EMAIL)) { | ||||
|                 $data['email'] = $this->client->present()->email(); | ||||
|             } | ||||
|             if($customer) | ||||
|                 return $customer; | ||||
| 
 | ||||
|             $customer = Customer::create($data, $this->stripe_connect_auth); | ||||
|         }  | ||||
| 
 | ||||
|         //Search by email
 | ||||
|         $searchResults = \Stripe\Customer::all([ | ||||
|                     "email" => $this->client->present()->email(), | ||||
|                     "limit" => 2, | ||||
|                     "starting_after" => null | ||||
|         ],$this->stripe_connect_auth); | ||||
| 
 | ||||
|         if(count($searchResults) == 1) | ||||
|             return $searchResults->data[0]; | ||||
|          | ||||
|         //Else create a new record
 | ||||
|         $data['name'] = $this->client->present()->name(); | ||||
|         $data['phone'] = $this->client->present()->phone(); | ||||
|          | ||||
|         if (filter_var($this->client->present()->email(), FILTER_VALIDATE_EMAIL)) { | ||||
|             $data['email'] = $this->client->present()->email(); | ||||
|         } | ||||
| 
 | ||||
|         $customer = Customer::create($data, $this->stripe_connect_auth); | ||||
| 
 | ||||
|         if (!$customer) { | ||||
|             throw new Exception('Unable to create gateway customer'); | ||||
|         } | ||||
|  | ||||
| @ -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); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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)]); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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);
 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
|  | ||||
| @ -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); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
| @ -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'); | ||||
| 
 | ||||
|  | ||||
| @ -145,4 +145,4 @@ | ||||
|     }, | ||||
|     "minimum-stability": "dev", | ||||
|     "prefer-stable": true | ||||
| } | ||||
| } | ||||
| @ -14,8 +14,8 @@ return [ | ||||
|     'require_https' => env('REQUIRE_HTTPS', true), | ||||
|     'app_url' => rtrim(env('APP_URL', ''), '/'), | ||||
|     'app_domain' => env('APP_DOMAIN', 'invoicing.co'), | ||||
|     'app_version' => '5.3.1', | ||||
|     'app_tag' => '5.3.1', | ||||
|     'app_version' => '5.3.2', | ||||
|     'app_tag' => '5.3.2', | ||||
|     'minimum_client_version' => '5.0.16', | ||||
|     'terms_version' => '1.0.1', | ||||
|     'api_secret' => env('API_SECRET', ''), | ||||
| @ -115,7 +115,7 @@ return [ | ||||
|         //'fonts' => 'App\Models\Font',
 | ||||
|     ], | ||||
|     'notification' => [ | ||||
|         'slack' => env('SLACK_WEBHOOK_URL', ''), | ||||
|         'slack' => env('SLACK_WEBHOOK_URL', false), | ||||
|         'mail' => env('HOSTED_EMAIL', ''), | ||||
|     ], | ||||
|     'themes' => [ | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
							
								
								
									
										2
									
								
								public/css/app.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/css/app.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										4
									
								
								public/flutter_service_worker.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								public/flutter_service_worker.js
									
									
									
									
										vendored
									
									
								
							| @ -33,8 +33,8 @@ const RESOURCES = { | ||||
| "manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40", | ||||
| "version.json": "46d4015fc9abcefe5371cafcf2084173", | ||||
| "favicon.ico": "51636d3a390451561744c42188ccd628", | ||||
| "main.dart.js": "6c0d755c0f7fe5211d33ac59d499dafe", | ||||
| "/": "76a5fa48cfed240c8326ae7736d8044d" | ||||
| "main.dart.js": "ad09f3d4a2f418fe67aa5e04dfde7fc7", | ||||
| "/": "557b6af2ed285b00c0499fe5e06805cd" | ||||
| }; | ||||
| 
 | ||||
| // The application shell files that are downloaded before a service worker can
 | ||||
|  | ||||
							
								
								
									
										2
									
								
								public/js/clients/payment_methods/braintree-ach.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								public/js/clients/payment_methods/braintree-ach.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| /*! For license information please see braintree-ach.js.LICENSE.txt */ | ||||
| !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=24)}({24:function(e,t,n){e.exports=n("cGea")},cGea:function(e,t){var n;window.braintree.client.create({authorization:null===(n=document.querySelector('meta[name="client-token"]'))||void 0===n?void 0:n.content}).then((function(e){return braintree.usBankAccount.create({client:e})})).then((function(e){var t;null===(t=document.getElementById("authorize-bank-account"))||void 0===t||t.addEventListener("click",(function(t){t.target.parentElement.disabled=!0,document.getElementById("errors").hidden=!0,document.getElementById("errors").textContent="";var n={accountNumber:document.getElementById("account-number").value,routingNumber:document.getElementById("routing-number").value,accountType:document.querySelector('input[name="account-type"]:checked').value,ownershipType:document.querySelector('input[name="ownership-type"]:checked').value,billingAddress:{streetAddress:document.getElementById("billing-street-address").value,extendedAddress:document.getElementById("billing-extended-address").value,locality:document.getElementById("billing-locality").value,region:document.getElementById("billing-region").value,postalCode:document.getElementById("billing-postal-code").value}};if("personal"===n.ownershipType){var r=document.getElementById("account-holder-name").value.split(" ",2);n.firstName=r[0],n.lastName=r[1]}else n.businessName=document.getElementById("account-holder-name").value;e.tokenize({bankDetails:n,mandateText:'By clicking ["Checkout"], I authorize Braintree, a service of PayPal, on behalf of [your business name here] (i) to verify my bank account information using bank information and consumer reports and (ii) to debit my bank account.'}).then((function(e){document.querySelector("input[name=nonce]").value=e.nonce,document.getElementById("server_response").submit()})).catch((function(e){t.target.parentElement.disabled=!1,document.getElementById("errors").textContent="".concat(e.details.originalError.message," ").concat(e.details.originalError.details.originalError[0].message),document.getElementById("errors").hidden=!1}))}))})).catch((function(e){document.getElementById("errors").textContent="".concat(error.details.originalError.message," ").concat(error.details.originalError.details.originalError[0].message),document.getElementById("errors").hidden=!1}))}}); | ||||
| @ -0,0 +1,9 @@ | ||||
| /** | ||||
|  * 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 | ||||
|  */ | ||||
| @ -1,2 +1,2 @@ | ||||
| /*! For license information please see paytrace-credit-card.js.LICENSE.txt */ | ||||
| !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=21)}({"0Swb":function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){var t;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.clientKey=null===(t=document.querySelector("meta[name=paytrace-client-key]"))||void 0===t?void 0:t.content}var t,r,o;return t=e,(r=[{key:"creditCardStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"100%"}}},{key:"codeStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"300px"}}},{key:"expStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"85px",type:"dropdown"}}},{key:"updatePayTraceLabels",value:function(){window.PTPayment.getControl("securityCode").label.text(document.querySelector("meta[name=ctrans-cvv]").content),window.PTPayment.getControl("creditCard").label.text(document.querySelector("meta[name=ctrans-card_number]").content),window.PTPayment.getControl("expiration").label.text(document.querySelector("meta[name=ctrans-expires]").content)}},{key:"setupPayTrace",value:function(){return window.PTPayment.setup({styles:{code:this.codeStyles,cc:this.creditCardStyles,exp:this.expStyles},authorization:{clientKey:this.clientKey}})}},{key:"handlePaymentWithCreditCard",value:function(e){var t=this;e.target.parentElement.disabled=!0,document.getElementById("errors").hidden=!0,window.PTPayment.validate((function(n){if(n.length>=1){var r=document.getElementById("errors");return r.textContent=n[0].description,r.hidden=!1,e.target.parentElement.disabled=!1}t.ptInstance.process().then((function(e){document.getElementById("HPF_Token").value=e.message.hpf_token,document.getElementById("enc_key").value=e.message.enc_key;var t=document.querySelector('input[name="token-billing-checkbox"]:checked');t&&(document.querySelector('input[name="store_card"]').value=t.value),document.getElementById("server_response").submit()})).catch((function(e){document.getElementById("errors").textContent=JSON.stringify(e),document.getElementById("errors").hidden=!1,console.log(e)}))}))}},{key:"handlePaymentWithToken",value:function(e){e.target.parentElement.disabled=!0,document.getElementById("server_response").submit()}},{key:"handle",value:function(){var e=this;this.setupPayTrace().then((function(t){var n;e.ptInstance=t,e.updatePayTraceLabels(),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token}))})),null===(n=document.getElementById("toggle-payment-with-credit-card"))||void 0===n||n.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value=""})),document.getElementById("pay-now").addEventListener("click",(function(t){return""===document.querySelector("input[name=token]").value?e.handlePaymentWithCreditCard(t):e.handlePaymentWithToken(t)}))}))}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()},21:function(e,t,n){e.exports=n("0Swb")}}); | ||||
| !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=21)}({"0Swb":function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){var t;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.clientKey=null===(t=document.querySelector("meta[name=paytrace-client-key]"))||void 0===t?void 0:t.content}var t,r,o;return t=e,(r=[{key:"creditCardStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"100%"}}},{key:"codeStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"300px"}}},{key:"expStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"85px",type:"dropdown"}}},{key:"updatePayTraceLabels",value:function(){window.PTPayment.getControl("securityCode").label.text(document.querySelector("meta[name=ctrans-cvv]").content),window.PTPayment.getControl("creditCard").label.text(document.querySelector("meta[name=ctrans-card_number]").content),window.PTPayment.getControl("expiration").label.text(document.querySelector("meta[name=ctrans-expires]").content)}},{key:"setupPayTrace",value:function(){return window.PTPayment.setup({styles:{code:this.codeStyles,cc:this.creditCardStyles,exp:this.expStyles},authorization:{clientKey:this.clientKey}})}},{key:"handlePaymentWithCreditCard",value:function(e){var t=this;e.target.parentElement.disabled=!0,document.getElementById("errors").hidden=!0,window.PTPayment.validate((function(n){if(n.length>=1){var r=document.getElementById("errors");return r.textContent=n[0].description,r.hidden=!1,e.target.parentElement.disabled=!1}t.ptInstance.process().then((function(e){document.getElementById("HPF_Token").value=e.message.hpf_token,document.getElementById("enc_key").value=e.message.enc_key;var t=document.querySelector('input[name="token-billing-checkbox"]:checked');t&&(document.querySelector('input[name="store_card"]').value=t.value),document.getElementById("server_response").submit()})).catch((function(e){document.getElementById("errors").textContent=JSON.stringify(e),document.getElementById("errors").hidden=!1,console.log(e)}))}))}},{key:"handlePaymentWithToken",value:function(e){e.target.parentElement.disabled=!0,document.getElementById("server_response").submit()}},{key:"handle",value:function(){var e,t=this;Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token}))})),null===(e=document.getElementById("toggle-payment-with-credit-card"))||void 0===e||e.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value="",t.setupPayTrace().then((function(e){t.ptInstance=e,t.updatePayTraceLabels()}))})),document.getElementById("pay-now").addEventListener("click",(function(e){return""===document.querySelector("input[name=token]").value?t.handlePaymentWithCreditCard(e):t.handlePaymentWithToken(e)}))}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()},21:function(e,t,n){e.exports=n("0Swb")}}); | ||||
							
								
								
									
										4
									
								
								public/main.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								public/main.dart.js
									
									
									
									
										vendored
									
									
								
							| @ -38997,7 +38997,7 @@ while(true)switch(s){case 0:p=a.a | ||||
| a.a=p==null?null:p | ||||
| if(a.db==null){q=a.r1.aVA(a.k4) | ||||
| a.db=q}p=a.cy | ||||
| a.cy=p==null?"ebe27c5902e4f81eb389257cb979a4091da52207":p | ||||
| a.cy=p==null?"576eabd27a218caae56535d3be029e6e855c5331":p | ||||
| p=a.go | ||||
| a.go=p==null?null:p | ||||
| return P.S(null,r)}}) | ||||
| @ -154235,7 +154235,7 @@ return s.a=J.f_(s.a,"\n \u2022 "+H.i(a))}, | ||||
| $S:9} | ||||
| F.ddo.prototype={ | ||||
| $1:function(a){a.a="https://634363c8dd6048b8ae89ab6c66dd9c24@sentry.invoicing.co/7" | ||||
| a.cy="ebe27c5902e4f81eb389257cb979a4091da52207" | ||||
| a.cy="576eabd27a218caae56535d3be029e6e855c5331" | ||||
| a.go="5.0.58" | ||||
| a.ch=new F.ddn(this.a)}, | ||||
| $S:741} | ||||
|  | ||||
| @ -1,10 +1,11 @@ | ||||
| { | ||||
|     "/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5", | ||||
|     "/css/app.css": "/css/app.css?id=56fdeb0a3b78b00b9a52", | ||||
|     "/css/app.css": "/css/app.css?id=27d1431e24a51260d3f1", | ||||
|     "/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/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7", | ||||
|     "/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=f7f4ecfb1771951b91e7", | ||||
|     "/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js?id=9fb7941baba1f9645ed9", | ||||
|     "/js/clients/payment_methods/wepay-bank-account.js": "/js/clients/payment_methods/wepay-bank-account.js?id=8fea0be371d430064a89", | ||||
|     "/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=7c2cbef525868592f42e", | ||||
|     "/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js?id=81957e7cb1cb49f23b90", | ||||
| @ -13,7 +14,7 @@ | ||||
|     "/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=065e5450233cc5b47020", | ||||
|     "/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/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c8d3808a4c02d1392e96", | ||||
|     "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c2b5f7831e1a46dd5fb2", | ||||
|     "/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-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344", | ||||
|  | ||||
							
								
								
									
										66
									
								
								resources/js/clients/payment_methods/braintree-ach.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								resources/js/clients/payment_methods/braintree-ach.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| /** | ||||
|  * 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
 | ||||
|  */ | ||||
| 
 | ||||
| window.braintree.client.create({ | ||||
|     authorization: document.querySelector('meta[name="client-token"]')?.content | ||||
| }).then(function (clientInstance) { | ||||
|     return braintree.usBankAccount.create({ | ||||
|         client: clientInstance | ||||
|     }); | ||||
| }).then(function (usBankAccountInstance) { | ||||
|     document | ||||
|         .getElementById('authorize-bank-account') | ||||
|         ?.addEventListener('click', (e) => { | ||||
|             e.target.parentElement.disabled = true; | ||||
| 
 | ||||
|             document.getElementById('errors').hidden = true; | ||||
|             document.getElementById('errors').textContent = ''; | ||||
| 
 | ||||
|             let bankDetails = { | ||||
|                 accountNumber: document.getElementById('account-number').value, | ||||
|                 routingNumber: document.getElementById('routing-number').value, | ||||
|                 accountType: document.querySelector('input[name="account-type"]:checked').value, | ||||
|                 ownershipType: document.querySelector('input[name="ownership-type"]:checked').value, | ||||
|                 billingAddress: { | ||||
|                     streetAddress: document.getElementById('billing-street-address').value, | ||||
|                     extendedAddress: document.getElementById('billing-extended-address').value, | ||||
|                     locality: document.getElementById('billing-locality').value, | ||||
|                     region: document.getElementById('billing-region').value, | ||||
|                     postalCode: document.getElementById('billing-postal-code').value | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (bankDetails.ownershipType === 'personal') { | ||||
|                 let name = document.getElementById('account-holder-name').value.split(' ', 2); | ||||
| 
 | ||||
|                 bankDetails.firstName = name[0]; | ||||
|                 bankDetails.lastName = name[1]; | ||||
|             } else { | ||||
|                 bankDetails.businessName = document.getElementById('account-holder-name').value; | ||||
|             } | ||||
| 
 | ||||
|             usBankAccountInstance.tokenize({ | ||||
|                 bankDetails, | ||||
|                 mandateText: 'By clicking ["Checkout"], I authorize Braintree, a service of PayPal, on behalf of [your business name here] (i) to verify my bank account information using bank information and consumer reports and (ii) to debit my bank account.' | ||||
|             }).then(function (payload) { | ||||
|                 document.querySelector('input[name=nonce]').value = payload.nonce; | ||||
|                 document.getElementById('server_response').submit(); | ||||
|             }) | ||||
|                 .catch(function (error) { | ||||
|                     e.target.parentElement.disabled = false; | ||||
| 
 | ||||
|                     document.getElementById('errors').textContent = `${error.details.originalError.message} ${error.details.originalError.details.originalError[0].message}`; | ||||
|                     document.getElementById('errors').hidden = false; | ||||
|                 }); | ||||
|         }); | ||||
| }).catch(function (err) { | ||||
|     document.getElementById('errors').textContent = `${error.details.originalError.message} ${error.details.originalError.details.originalError[0].message}`; | ||||
|     document.getElementById('errors').hidden = false; | ||||
| }); | ||||
| @ -137,49 +137,49 @@ class PayTraceCreditCard { | ||||
|     } | ||||
| 
 | ||||
|     handle() { | ||||
|         this.setupPayTrace().then((instance) => { | ||||
|             this.ptInstance = instance; | ||||
|             this.updatePayTraceLabels(); | ||||
|         Array.from( | ||||
|             document.getElementsByClassName('toggle-payment-with-token') | ||||
|         ).forEach((element) => | ||||
|             element.addEventListener('click', (element) => { | ||||
|                 document | ||||
|                     .getElementById('paytrace--credit-card-container') | ||||
|                     .classList.add('hidden'); | ||||
|                 document.getElementById( | ||||
|                     'save-card--container' | ||||
|                 ).style.display = 'none'; | ||||
|                 document.querySelector('input[name=token]').value = | ||||
|                     element.target.dataset.token; | ||||
|             }) | ||||
|         ); | ||||
| 
 | ||||
|             Array.from( | ||||
|                 document.getElementsByClassName('toggle-payment-with-token') | ||||
|             ).forEach((element) => | ||||
|                 element.addEventListener('click', (element) => { | ||||
|                     document | ||||
|                         .getElementById('paytrace--credit-card-container') | ||||
|                         .classList.add('hidden'); | ||||
|                     document.getElementById( | ||||
|                         'save-card--container' | ||||
|                     ).style.display = 'none'; | ||||
|                     document.querySelector('input[name=token]').value = | ||||
|                         element.target.dataset.token; | ||||
|                 }) | ||||
|             ); | ||||
|         document | ||||
|             .getElementById('toggle-payment-with-credit-card') | ||||
|             ?.addEventListener('click', (element) => { | ||||
|                 document | ||||
|                     .getElementById('paytrace--credit-card-container') | ||||
|                     .classList.remove('hidden'); | ||||
|                 document.getElementById( | ||||
|                     'save-card--container' | ||||
|                 ).style.display = 'grid'; | ||||
|                 document.querySelector('input[name=token]').value = ''; | ||||
| 
 | ||||
|             document | ||||
|                 .getElementById('toggle-payment-with-credit-card') | ||||
|                 ?.addEventListener('click', (element) => { | ||||
|                     document | ||||
|                         .getElementById('paytrace--credit-card-container') | ||||
|                         .classList.remove('hidden'); | ||||
|                     document.getElementById( | ||||
|                         'save-card--container' | ||||
|                     ).style.display = 'grid'; | ||||
|                     document.querySelector('input[name=token]').value = ''; | ||||
|                 this.setupPayTrace().then((instance) => { | ||||
|                     this.ptInstance = instance; | ||||
|                     this.updatePayTraceLabels(); | ||||
|                 }); | ||||
|             }); | ||||
| 
 | ||||
|             document | ||||
|                 .getElementById('pay-now') | ||||
|                 .addEventListener('click', (e) => { | ||||
|                     if ( | ||||
|                         document.querySelector('input[name=token]').value === '' | ||||
|                     ) { | ||||
|                         return this.handlePaymentWithCreditCard(e); | ||||
|                     } | ||||
|         document | ||||
|             .getElementById('pay-now') | ||||
|             .addEventListener('click', (e) => { | ||||
|                 if ( | ||||
|                     document.querySelector('input[name=token]').value === '' | ||||
|                 ) { | ||||
|                     return this.handlePaymentWithCreditCard(e); | ||||
|                 } | ||||
| 
 | ||||
|                     return this.handlePaymentWithToken(e); | ||||
|                 }); | ||||
|         }); | ||||
|                 return this.handlePaymentWithToken(e); | ||||
|             }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -4297,7 +4297,11 @@ $LANG = array( | ||||
|     'lang_Latvian' => 'Latvian', | ||||
|     'expiry_date' => 'Expiry date', | ||||
|     'cardholder_name' => 'Card holder name', | ||||
|      | ||||
|     'account_type' => 'Account type', | ||||
|     'locality' => 'Locality', | ||||
|     'checking' => 'Checking', | ||||
|     'savings' => 'Savings', | ||||
|     'unable_to_verify_payment_method' => 'Unable to verify payment method.', | ||||
| ); | ||||
| 
 | ||||
| return $LANG; | ||||
|  | ||||
							
								
								
									
										4
									
								
								resources/sass/components/buttons.scss
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								resources/sass/components/buttons.scss
									
									
									
									
										vendored
									
									
								
							| @ -35,8 +35,10 @@ button:disabled { | ||||
| } | ||||
| 
 | ||||
| .button-link { | ||||
|     @apply text-gray-700; | ||||
| 
 | ||||
|     &:hover { | ||||
|         @apply font-semibold underline; | ||||
|         @apply text-gray-900 underline; | ||||
|     } | ||||
| 
 | ||||
|     &:focus { | ||||
|  | ||||
| @ -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 }} | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| @extends('errors::minimal') | ||||
| @extends('portal.ninja2020.layout.error') | ||||
| 
 | ||||
| @section('title', __('Unauthorized')) | ||||
| @section('code', '401') | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| @extends('errors::minimal') | ||||
| @extends('portal.ninja2020.layout.error') | ||||
| 
 | ||||
| @section('title', __('Forbidden')) | ||||
| @section('code', '403') | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| @extends('errors::minimal') | ||||
| @extends('portal.ninja2020.layout.error') | ||||
| 
 | ||||
| @section('title', __('Not Found')) | ||||
| @section('code', '404') | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| @extends('errors::minimal') | ||||
| @extends('portal.ninja2020.layout.error') | ||||
| 
 | ||||
| @section('title', __('Page Expired')) | ||||
| @section('code', '419') | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| @extends('errors::minimal') | ||||
| @extends('portal.ninja2020.layout.error') | ||||
| 
 | ||||
| @section('title', __('Too Many Requests')) | ||||
| @section('code', '429') | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| @extends('errors::minimal') | ||||
| @extends('portal.ninja2020.layout.error') | ||||
| 
 | ||||
| @section('title', __('Server Error')) | ||||
| @section('code', '500') | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| @extends('errors::minimal') | ||||
| @extends('portal.ninja2020.layout.error') | ||||
| 
 | ||||
| @section('title', __('Service Unavailable')) | ||||
| @section('code', '503') | ||||
|  | ||||
| @ -41,7 +41,7 @@ | ||||
|                             </span> | ||||
|                     </th> | ||||
|                     <th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white  uppercase tracking-wider"> | ||||
|                             <span role="button" wire:click="sortBy('type_id')" class="cursor-pointer"> | ||||
|                             <span role="button" wire:click="sortBy('gateway_type_id')" class="cursor-pointer"> | ||||
|                                 {{ ctrans('texts.payment_type_id') }} | ||||
|                             </span> | ||||
|                     </th> | ||||
|  | ||||
| @ -0,0 +1,5 @@ | ||||
| <meta http-equiv="cache-control" content="max-age=0" /> | ||||
| <meta http-equiv="cache-control" content="no-cache" /> | ||||
| <meta http-equiv="expires" content="0" /> | ||||
| <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" /> | ||||
| <meta http-equiv="pragma" content="no-cache" /> | ||||
| @ -3,6 +3,8 @@ | ||||
| 
 | ||||
| @push('head') | ||||
|     <meta name="pdf-url" content="{{ $credit->pdf_file_path(null, 'url', true) }}"> | ||||
|     @include('portal.ninja2020.components.no-cache') | ||||
|      | ||||
|     <script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script> | ||||
| @endpush | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,95 @@ | ||||
| @extends('portal.ninja2020.layout.payments', ['gateway_title' => 'ACH', 'card_title' => 'ACH']) | ||||
| 
 | ||||
| @section('gateway_head') | ||||
|     <meta name="client-token" content="{{ $client_token ?? '' }}"/> | ||||
| @endsection | ||||
| 
 | ||||
| @section('gateway_content') | ||||
|     @if(session()->has('ach_error')) | ||||
|         <div class="alert alert-failure mb-4"> | ||||
|             <p>{{ session('ach_error') }}</p> | ||||
|         </div> | ||||
|     @endif | ||||
| 
 | ||||
|     <form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::BANK_TRANSFER]) }}" | ||||
|           method="post" id="server_response"> | ||||
|         @csrf | ||||
| 
 | ||||
|         <input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}"> | ||||
|         <input type="hidden" name="gateway_type_id" value="2"> | ||||
|         <input type="hidden" name="gateway_response" id="gateway_response"> | ||||
|         <input type="hidden" name="is_default" id="is_default"> | ||||
|         <input type="hidden" name="nonce" hidden> | ||||
|     </form> | ||||
| 
 | ||||
|     <div class="alert alert-failure mb-4" hidden id="errors"></div> | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_type')]) | ||||
|         <span class="flex items-center mr-4"> | ||||
|             <input class="form-radio mr-2" type="radio" value="checking" name="account-type" checked> | ||||
|             <span>{{ __('texts.checking') }}</span> | ||||
|         </span> | ||||
|         <span class="flex items-center mt-2"> | ||||
|             <input class="form-radio mr-2" type="radio" value="savings" name="account-type"> | ||||
|             <span>{{ __('texts.savings') }}</span> | ||||
|         </span> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_type')]) | ||||
|         <span class="flex items-center mr-4"> | ||||
|             <input class="form-radio mr-2" type="radio" value="personal" name="ownership-type" checked> | ||||
|             <span>{{ __('texts.individual_account') }}</span> | ||||
|         </span> | ||||
|         <span class="flex items-center mt-2"> | ||||
|             <input class="form-radio mr-2" type="radio" value="business" name="ownership-type"> | ||||
|             <span>{{ __('texts.company_account') }}</span> | ||||
|         </span> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_name')]) | ||||
|         <input class="input w-full" id="account-holder-name" type="text" placeholder="{{ ctrans('texts.name') }}" | ||||
|                required> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_number')]) | ||||
|         <input class="input w-full" id="account-number" type="text" required> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.routing_number')]) | ||||
|         <input class="input w-full" id="routing-number" type="text" required> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.address1')]) | ||||
|         <input class="input w-full" id="billing-street-address" type="text" required> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.address2')]) | ||||
|         <input class="input w-full" id="billing-extended-address" type="text" required> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.locality')]) | ||||
|         <input class="input w-full" id="billing-locality" type="text" required> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.state')]) | ||||
|         <select class="input w-full" id="billing-region"> | ||||
|             @foreach(\App\DataProviders\USStates::get() as $code => $state) | ||||
|                 <option value="{{ $code }}">{{ $state }} ({{ $code }})</option> | ||||
|             @endforeach | ||||
|         </select> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.postal_code')]) | ||||
|         <input class="input w-full" id="billing-postal-code" type="text" required> | ||||
|     @endcomponent | ||||
| 
 | ||||
|     @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-bank-account']) | ||||
|         {{ ctrans('texts.add_payment_method') }} | ||||
|     @endcomponent | ||||
| @endsection | ||||
| 
 | ||||
| @section('gateway_footer') | ||||
|     <script src="https://js.braintreegateway.com/web/3.81.0/js/client.min.js"></script> | ||||
|     <script src="https://js.braintreegateway.com/web/3.81.0/js/us-bank-account.min.js"></script> | ||||
|     <script src="{{ asset('js/clients/payment_methods/braintree-ach.js') }}"></script> | ||||
| @endsection | ||||
| @ -0,0 +1,59 @@ | ||||
| @extends('portal.ninja2020.layout.payments', ['gateway_title' => 'ACH', 'card_title' => 'ACH']) | ||||
| 
 | ||||
| @section('gateway_content') | ||||
|     @if(count($tokens) > 0) | ||||
|         <div class="alert alert-failure mb-4" hidden id="errors"></div> | ||||
| 
 | ||||
|         @include('portal.ninja2020.gateways.includes.payment_details') | ||||
| 
 | ||||
|         <form action="{{ route('client.payments.response') }}" method="post" id="server-response"> | ||||
|             @csrf | ||||
|             <input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}"> | ||||
|             <input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}"> | ||||
|             <input type="hidden" name="source" value=""> | ||||
|             <input type="hidden" name="amount" value="{{ $amount }}"> | ||||
|             <input type="hidden" name="currency" value="{{ $currency }}"> | ||||
|             <input type="hidden" name="payment_hash" value="{{ $payment_hash }}"> | ||||
|         </form> | ||||
| 
 | ||||
|         @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) | ||||
|             @if(count($tokens) > 0) | ||||
|                 @foreach($tokens as $token) | ||||
|                     <label class="mr-4"> | ||||
|                         <input | ||||
|                             type="radio" | ||||
|                             data-token="{{ $token->hashed_id }}" | ||||
|                             name="payment-type" | ||||
|                             class="form-radio cursor-pointer toggle-payment-with-token"/> | ||||
|                         <span class="ml-1 cursor-pointer">{{ ctrans('texts.bank_transfer') }} (*{{ $token->meta->last4 }})</span> | ||||
|                     </label> | ||||
|                 @endforeach | ||||
|             @endisset | ||||
|         @endcomponent | ||||
| 
 | ||||
|         @include('portal.ninja2020.gateways.includes.pay_now') | ||||
| 
 | ||||
|     @else | ||||
|         @component('portal.ninja2020.components.general.card-element-single', ['title' => 'ACH', 'show_title' => false]) | ||||
|             <span class="block">{{ ctrans('texts.bank_account_not_linked') }}</span> | ||||
|             <a class="hover:underline text-primary mt-2 block" | ||||
|                href="{{ route('client.payment_methods.index') }}">{{ ctrans('texts.add_payment_method') }}</a> | ||||
|         @endcomponent | ||||
|     @endif | ||||
| @endsection | ||||
| 
 | ||||
| @push('footer') | ||||
|     <script> | ||||
|         Array | ||||
|             .from(document.getElementsByClassName('toggle-payment-with-token')) | ||||
|             .forEach((element) => element.addEventListener('click', (element) => { | ||||
|                 document.querySelector('input[name=source]').value = element.target.dataset.token; | ||||
|             })); | ||||
| 
 | ||||
|         document.getElementById('pay-now').addEventListener('click', function (e) { | ||||
|             e.target.parentElement.disabled = true; | ||||
| 
 | ||||
|             document.getElementById('server-response').submit(); | ||||
|         }); | ||||
|     </script> | ||||
| @endpush | ||||
| @ -4,6 +4,8 @@ | ||||
| @push('head') | ||||
|     <meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}"> | ||||
|     <meta name="require-invoice-signature" content="{{ $client->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_invoice_signature }}"> | ||||
|     @include('portal.ninja2020.components.no-cache') | ||||
|      | ||||
|     <script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script> | ||||
| @endpush | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										26
									
								
								resources/views/portal/ninja2020/layout/error.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								resources/views/portal/ninja2020/layout/error.blade.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| @extends('portal.ninja2020.layout.clean') | ||||
| @section('meta_title', $__env->yieldContent('title')) | ||||
| 
 | ||||
| @section('body') | ||||
|     <div class="grid lg:grid-cols-3"> | ||||
|         <div class="hidden lg:block col-span-1 bg-red-100 h-screen"> | ||||
|             <img src="{{ asset('images/client-portal-new-image.jpg') }}" | ||||
|                  class="w-full h-screen object-cover" | ||||
|                  alt="Background image"> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="col-span-2 h-screen flex"> | ||||
|             <div class="m-auto md:w-1/2 lg:w-1/4 flex flex-col items-center"> | ||||
|                 <span class="flex items-center text-2xl"> | ||||
|                     @yield('code') — @yield('message') | ||||
|                 </span> | ||||
| 
 | ||||
|                 <a class="button-link text-sm mt-2" href="{{ request()->getSchemeAndHttpHost() }}"> | ||||
|                     {{ ctrans('texts.back_to', ['url' => parse_url(request()->getHttpHost())['host'] ?? request()->getHttpHost()]) }} | ||||
|                 </a> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| @endsection | ||||
| 
 | ||||
| 
 | ||||
| @ -8,6 +8,8 @@ | ||||
|     <meta name="show-quote-terms" content="{{ $settings->show_accept_quote_terms ? true : false }}"> | ||||
|     <meta name="require-quote-signature" content="{{ $client->company->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_quote_signature }}"> | ||||
| 
 | ||||
|     @include('portal.ninja2020.components.no-cache') | ||||
| 
 | ||||
|     <script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script> | ||||
| @endpush | ||||
| 
 | ||||
|  | ||||
| @ -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'); | ||||
|  | ||||
| @ -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'); | ||||
|  | ||||
							
								
								
									
										92
									
								
								tests/Browser/ClientPortal/Gateways/Braintree/ACHTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								tests/Browser/ClientPortal/Gateways/Braintree/ACHTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Tests\Browser\ClientPortal\Gateways\Braintree; | ||||
| 
 | ||||
| use App\DataMapper\FeesAndLimits; | ||||
| use App\Models\Company; | ||||
| use App\Models\CompanyGateway; | ||||
| use App\Models\GatewayType; | ||||
| use Illuminate\Foundation\Testing\DatabaseMigrations; | ||||
| use Laravel\Dusk\Browser; | ||||
| use Tests\Browser\Pages\ClientPortal\Login; | ||||
| use Tests\DuskTestCase; | ||||
| 
 | ||||
| class ACHTest extends DuskTestCase | ||||
| { | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         parent::setUp(); | ||||
| 
 | ||||
|         foreach (static::$browsers as $browser) { | ||||
|             $browser->driver->manage()->deleteAllCookies(); | ||||
|         } | ||||
| 
 | ||||
|         $this->browse(function (Browser $browser) { | ||||
|             $browser | ||||
|                 ->visit(new Login()) | ||||
|                 ->auth(); | ||||
|         }); | ||||
| 
 | ||||
|         $this->disableCompanyGateways(); | ||||
| 
 | ||||
|         CompanyGateway::where('gateway_key', 'f7ec488676d310683fb51802d076d713')->restore(); | ||||
| 
 | ||||
|         $cg = CompanyGateway::where('gateway_key', 'f7ec488676d310683fb51802d076d713')->firstOrFail(); | ||||
|         $fees_and_limits = $cg->fees_and_limits; | ||||
|         $fees_and_limits->{GatewayType::BANK_TRANSFER} = new FeesAndLimits(); | ||||
|         $cg->fees_and_limits = $fees_and_limits; | ||||
|         $cg->save(); | ||||
| 
 | ||||
|         $company = Company::first(); | ||||
|         $settings = $company->settings; | ||||
| 
 | ||||
|         $settings->client_portal_allow_under_payment = true; | ||||
|         $settings->client_portal_allow_over_payment = true; | ||||
| 
 | ||||
|         $company->settings = $settings; | ||||
|         $company->save(); | ||||
|     } | ||||
| 
 | ||||
|     public function testAddingBankAccount() | ||||
|     { | ||||
|         $this->browse(function (Browser $browser) { | ||||
|             $browser | ||||
|                 ->visitRoute('client.payment_methods.index') | ||||
|                 ->press('Add Payment Method') | ||||
|                 ->clickLink('Bank Account') | ||||
|                 ->type('#account-holder-name', 'John Doe') | ||||
|                 ->type('#account-number', '1000000000') | ||||
|                 ->type('#routing-number', '011000015') | ||||
|                 ->type('#billing-postal-code', '12345') | ||||
|                 ->press('Add Payment Method') | ||||
|                 ->waitForText('Added payment method.'); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public function testPayingWithExistingACH() | ||||
|     { | ||||
|         $this->browse(function (Browser $browser) { | ||||
|             $browser | ||||
|                 ->visitRoute('client.invoices.index') | ||||
|                 ->click('@pay-now') | ||||
|                 ->press('Pay Now') | ||||
|                 ->clickLink('Bank Transfer') | ||||
|                 ->click('.toggle-payment-with-token') | ||||
|                 ->press('Pay Now') | ||||
|                 ->waitForText('Details of the payment', 60); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public function testRemoveACHAccount() | ||||
|     { | ||||
|         $this->browse(function (Browser $browser) { | ||||
|             $browser | ||||
|                 ->visitRoute('client.payment_methods.index') | ||||
|                 ->clickLink('View') | ||||
|                 ->press('Remove Payment Method') | ||||
|                 ->waitForText('Confirmation') | ||||
|                 ->click('@confirm-payment-removal') | ||||
|                 ->assertSee('Payment method has been successfully removed.'); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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) {
 | ||||
|  | ||||
							
								
								
									
										4
									
								
								webpack.mix.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								webpack.mix.js
									
									
									
									
										vendored
									
									
								
							| @ -93,6 +93,10 @@ mix.js("resources/js/app.js", "public/js") | ||||
|     .js( | ||||
|         "resources/js/clients/payments/eway-credit-card.js", | ||||
|         "public/js/clients/payments/eway-credit-card.js" | ||||
|     ) | ||||
|     .js( | ||||
|         "resources/js/clients/payment_methods/braintree-ach.js", | ||||
|         "public/js/clients/payment_methods/braintree-ach.js" | ||||
|     ); | ||||
| 
 | ||||
| mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css'); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user