mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-15 15:42:51 +01:00
commit
2a9cb7f7ad
@ -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']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
||||
$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;
|
||||
|
@ -110,7 +110,8 @@ class Gateway extends StaticModel
|
||||
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)
|
||||
{
|
||||
|
||||
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,6 +378,8 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
$this->payment_hash
|
||||
);
|
||||
|
||||
if (!is_null($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;
|
||||
@ -388,6 +389,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
$invoices->each(function ($invoice) {
|
||||
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email)
|
||||
$invoice->service()->deletePdf();
|
||||
|
||||
});
|
||||
@ -398,12 +400,14 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
$nmo->to_user = $invitation->contact;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$gateway->payment_hash,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
@ -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,7 +74,8 @@ class BraintreePaymentDriver extends BaseDriver
|
||||
{
|
||||
$types = [
|
||||
GatewayType::PAYPAL,
|
||||
GatewayType::CREDIT_CARD
|
||||
GatewayType::CREDIT_CARD,
|
||||
GatewayType::BANK_TRANSFER,
|
||||
];
|
||||
|
||||
return $types;
|
||||
@ -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,10 +315,27 @@ 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 {
|
||||
|
||||
$customer = Customer::retrieve($client_gateway_token->gateway_customer_reference, $this->stripe_connect_auth);
|
||||
|
||||
if($customer)
|
||||
return $customer;
|
||||
|
||||
}
|
||||
|
||||
//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();
|
||||
|
||||
@ -327,7 +344,6 @@ class StripePaymentDriver extends BaseDriver
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -262,6 +262,7 @@ https://developer.wepay.com/api/api-calls/checkout
|
||||
private function storePaymentMethod($response, $payment_method_id)
|
||||
{
|
||||
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;
|
||||
|
||||
$this->setStatus(Invoice::STATUS_PAID)->workFlow();
|
||||
// InvoiceWorkflowSettings::dispatchNow($this->invoice);
|
||||
}
|
||||
// if ((int)$this->invoice->balance == 0) {
|
||||
|
||||
// $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();
|
||||
|
||||
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');
|
||||
|
||||
|
@ -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,10 +137,6 @@ class PayTraceCreditCard {
|
||||
}
|
||||
|
||||
handle() {
|
||||
this.setupPayTrace().then((instance) => {
|
||||
this.ptInstance = instance;
|
||||
this.updatePayTraceLabels();
|
||||
|
||||
Array.from(
|
||||
document.getElementsByClassName('toggle-payment-with-token')
|
||||
).forEach((element) =>
|
||||
@ -166,6 +162,11 @@ class PayTraceCreditCard {
|
||||
'save-card--container'
|
||||
).style.display = 'grid';
|
||||
document.querySelector('input[name=token]').value = '';
|
||||
|
||||
this.setupPayTrace().then((instance) => {
|
||||
this.ptInstance = instance;
|
||||
this.updatePayTraceLabels();
|
||||
});
|
||||
});
|
||||
|
||||
document
|
||||
@ -179,7 +180,6 @@ class PayTraceCreditCard {
|
||||
|
||||
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…
Reference in New Issue
Block a user