Merge pull request #6525 from turbo124/v5-develop

Fixes for auto-bill
This commit is contained in:
David Bomba 2021-08-31 18:22:35 +10:00 committed by GitHub
commit 8b399b3f64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 376 additions and 65 deletions

View File

@ -16,6 +16,9 @@ use App\Events\Invoice\InvoiceWasViewed;
use App\Events\Misc\InvitationWasViewed; use App\Events\Misc\InvitationWasViewed;
use App\Events\Quote\QuoteWasViewed; use App\Events\Quote\QuoteWasViewed;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Payment;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash; 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 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);
}
} }

View File

@ -69,9 +69,13 @@ class CompanyController extends BaseController
*/ */
public function __construct(CompanyRepository $company_repo) public function __construct(CompanyRepository $company_repo)
{ {
parent::__construct(); parent::__construct();
$this->company_repo = $company_repo; $this->company_repo = $company_repo;
$this->middleware('password_protected')->only(['destroy']);
} }
/** /**

View File

@ -127,12 +127,11 @@ class EmailController extends BaseController
$entity_obj->invitations->each(function ($invitation) use ($data, $entity_string, $entity_obj, $template) { $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(); $entity_obj->service()->markSent()->save();
EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data); EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data);
// ->delay(now()->addSeconds(45));
} }

View File

@ -66,7 +66,8 @@ class ImportJsonController extends BaseController
$file_location = $request->file('files') $file_location = $request->file('files')
->storeAs( ->storeAs(
'migrations', 'migrations',
$request->file('files')->getClientOriginalName() $request->file('files')->getClientOriginalName(),
config('filesystems.default'),
); );
if(Ninja::isHosted()) if(Ninja::isHosted())

View File

@ -25,6 +25,7 @@ use App\Utils\Ninja;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Facades\App;
class MigrationController extends BaseController class MigrationController extends BaseController
{ {
@ -263,6 +264,10 @@ class MigrationController extends BaseController
// Look for possible existing company (based on company keys). // Look for possible existing company (based on company keys).
$existing_company = Company::whereRaw('BINARY `company_key` = ?', [$company->company_key])->first(); $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) { if(!$existing_company && $company_count >=10) {
$nmo = new NinjaMailerObject; $nmo = new NinjaMailerObject;

View File

@ -213,7 +213,7 @@ class PostMarkController extends BaseController
$request->input('MessageID') $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); SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
} }

View File

@ -21,7 +21,7 @@ class UpdateAutoBilling extends Component
public function updateAutoBilling(): void 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->auto_bill_enabled = !$this->invoice->auto_bill_enabled;
$this->invoice->save(); $this->invoice->save();
} }

View File

@ -52,7 +52,8 @@ class PasswordProtection
$x_api_password = base64_decode($request->header('X-API-PASSWORD-BASE64')); $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); Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);

View File

@ -101,8 +101,8 @@ class UpdateRecurringInvoiceRequest extends Request
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
} }
if (isset($input['auto_bill'])) { if (array_key_exists('auto_bill', $input) && isset($input['auto_bill']) && $this->setAutoBillFlag($input['auto_bill'])) {
$input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']); $input['auto_bill_enabled'] = true;
} }
if (array_key_exists('documents', $input)) { if (array_key_exists('documents', $input)) {
@ -123,13 +123,8 @@ class UpdateRecurringInvoiceRequest extends Request
*/ */
private function setAutoBillFlag($auto_bill) :bool private function setAutoBillFlag($auto_bill) :bool
{ {
if ($auto_bill == 'always') { if ($auto_bill == 'always')
return true; 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; return false;
} }

View File

@ -146,7 +146,7 @@ class BaseTransformer
$number = 0; $number = 0;
} }
return Number::parseStringFloat($number); return Number::parseFloat($number);
} }
/** /**

View File

@ -25,7 +25,7 @@ class ExpenseTransformer extends BaseTransformer {
'date' => isset( $data['expense.date'] ) ? date( 'Y-m-d', strtotime( $data['expense.date'] ) ) : null, 'date' => isset( $data['expense.date'] ) ? date( 'Y-m-d', strtotime( $data['expense.date'] ) ) : null,
'public_notes' => $this->getString( $data, 'expense.public_notes' ), 'public_notes' => $this->getString( $data, 'expense.public_notes' ),
'private_notes' => $this->getString( $data, 'expense.private_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, '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_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, 'payment_date' => isset( $data['expense.payment_date'] ) ? date( 'Y-m-d', strtotime( $data['expense.payment_date'] ) ) : null,

View File

@ -498,6 +498,7 @@ class CompanyExport implements ShouldQueue
if(Ninja::isHosted()) { if(Ninja::isHosted()) {
Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path)); Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path));
unlink($zip_path);
} }
App::forgetInstance('translator'); App::forgetInstance('translator');

View File

@ -224,7 +224,7 @@ class CompanyImport implements ShouldQueue
// if(mime_content_type(Storage::path($this->file_location)) == 'text/plain') // if(mime_content_type(Storage::path($this->file_location)) == 'text/plain')
// return Storage::path($this->file_location); // 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(); $zip = new ZipArchive();
$archive = $zip->open($path); $archive = $zip->open($path);
@ -235,7 +235,7 @@ class CompanyImport implements ShouldQueue
$zip->close(); $zip->close();
$file_location = "{$file_path}/backup.json"; $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.'); throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
return $file_location; return $file_location;
@ -568,7 +568,7 @@ class CompanyImport implements ShouldQueue
{ {
$this->genericImport(GroupSetting::class, $this->genericImport(GroupSetting::class,
['user_id', 'company_id', 'id', 'hashed_id',], ['user_id', 'company_id', 'id', 'hashed_id'],
[['users' => 'user_id']], [['users' => 'user_id']],
'group_settings', 'group_settings',
'name'); 'name');
@ -580,7 +580,7 @@ class CompanyImport implements ShouldQueue
{ {
$this->genericImport(Subscription::class, $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']], [['group_settings' => 'group_id'], ['users' => 'user_id'], ['users' => 'assigned_user_id']],
'subscriptions', 'subscriptions',
'name'); 'name');

View File

@ -224,7 +224,7 @@ class NinjaMailerJob implements ShouldQueue
return true; 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 */ /* 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; return true;
/* GMail users are uncapped */ /* GMail users are uncapped */

View File

@ -55,8 +55,8 @@ class QuoteWorkflowSettings implements ShouldQueue
}); });
} }
if ($this->client->getSetting('auto_archive_quote')) { // if ($this->client->getSetting('auto_archive_quote')) {
$this->base_repository->archive($this->quote); // $this->base_repository->archive($this->quote);
} // }
} }
} }

View File

@ -116,7 +116,7 @@ class SendRecurring implements ShouldQueue
nlog("Invoice {$invoice->number} created"); nlog("Invoice {$invoice->number} created");
$invoice->invitations->each(function ($invitation) use ($invoice) { $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{ try{
EmailEntity::dispatch($invitation, $invoice->company); EmailEntity::dispatch($invitation, $invoice->company);

View File

@ -262,8 +262,6 @@ class Import implements ShouldQueue
/*After a migration first some basic jobs to ensure the system is up to date*/ /*After a migration first some basic jobs to ensure the system is up to date*/
VersionCheck::dispatch(); VersionCheck::dispatch();
// CreateCompanyPaymentTerms::dispatchNow($sp035a66, $spaa9f78); // CreateCompanyPaymentTerms::dispatchNow($sp035a66, $spaa9f78);
// CreateCompanyTaskStatuses::dispatchNow($this->company, $this->user); // CreateCompanyTaskStatuses::dispatchNow($this->company, $this->user);

View File

@ -63,7 +63,7 @@ class SendFailedEmails implements ShouldQueue
$invitation = $job_meta_array['entity_name']::where('key', $job_meta_array['invitation_key'])->with('contact')->first(); $invitation = $job_meta_array['entity_name']::where('key', $job_meta_array['invitation_key'])->with('contact')->first();
if ($invitation->invoice) { 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']); EmailEntity::dispatch($invitation, $invitation->company, $job_meta_array['reminder_template']);
} }
} }

View File

@ -29,6 +29,7 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use ZipArchive; use ZipArchive;
use Illuminate\Support\Facades\App;
class StartMigration implements ShouldQueue class StartMigration implements ShouldQueue
{ {
@ -122,6 +123,10 @@ class StartMigration implements ShouldQueue
$this->company->update_products = $update_product_flag; $this->company->update_products = $update_product_flag;
$this->company->save(); $this->company->save();
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing | \Exception $e) { } catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing | \Exception $e) {
$this->company->update_products = $update_product_flag; $this->company->update_products = $update_product_flag;

View File

@ -55,6 +55,10 @@ class SystemLogger implements ShouldQueue
MultiDB::setDb($this->company->db); MultiDB::setDb($this->company->db);
$client_id = $this->client ? $this->client->id : null; $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; $user_id = $this->client ? $this->client->user_id : $this->company->owner()->id;
$sl = [ $sl = [

View File

@ -24,6 +24,7 @@ use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
class SendVerificationNotification implements ShouldQueue class SendVerificationNotification implements ShouldQueue
{ {
@ -53,6 +54,10 @@ class SendVerificationNotification implements ShouldQueue
$event->user->service()->invite($event->company); $event->user->service()->invite($event->company);
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($event->company->settings));
$nmo = new NinjaMailerObject; $nmo = new NinjaMailerObject;
$nmo->mailable = new UserAdded($event->company, $event->creating_user, $event->user); $nmo->mailable = new UserAdded($event->company, $event->creating_user, $event->user);
$nmo->company = $event->company; $nmo->company = $event->company;

View File

@ -132,7 +132,6 @@ class InvoiceEmailEngine extends BaseEmailEngine
} }
} }
return $this; return $this;

View File

@ -45,8 +45,8 @@ class SupportMessageSent extends Mailable
$log_file->seek(PHP_INT_MAX); $log_file->seek(PHP_INT_MAX);
$last_line = $log_file->key(); $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); $log_lines = iterator_to_array($lines);
} }
@ -76,6 +76,7 @@ class SupportMessageSent extends Mailable
'system_info' => $system_info, 'system_info' => $system_info,
'laravel_log' => $log_lines, 'laravel_log' => $log_lines,
'logo' => $company->present()->logo(), 'logo' => $company->present()->logo(),
'settings' => $company->settings
]); ]);
} }
} }

View File

@ -11,6 +11,8 @@
namespace App\Mail; namespace App\Mail;
use App\Jobs\Invoice\CreateUbl;
use App\Models\Account;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\User; 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; return $this;
} }
} }

View File

@ -34,6 +34,15 @@ class GroupSetting extends StaticModel
'settings', 'settings',
]; ];
protected $appends = [
'hashed_id',
];
public function getHashedIdAttribute()
{
return $this->encodePrimaryKey($this->id);
}
protected $touches = []; protected $touches = [];
public function company() public function company()

View File

@ -84,10 +84,6 @@ class Invoice extends BaseModel
'custom_surcharge2', 'custom_surcharge2',
'custom_surcharge3', 'custom_surcharge3',
'custom_surcharge4', 'custom_surcharge4',
// 'custom_surcharge_tax1',
// 'custom_surcharge_tax2',
// 'custom_surcharge_tax3',
// 'custom_surcharge_tax4',
'design_id', 'design_id',
'assigned_user_id', 'assigned_user_id',
'exchange_rate', 'exchange_rate',

View File

@ -287,8 +287,24 @@ class Payment extends BaseModel
event(new PaymentWasVoided($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); 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;
}
} }

View File

@ -389,6 +389,7 @@ class BaseDriver extends AbstractPaymentDriver
$invoices->each(function ($invoice) { $invoices->each(function ($invoice) {
if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) {
$invoice->service()->deletePdf(); $invoice->service()->deletePdf();
}); });
@ -455,7 +456,7 @@ class BaseDriver extends AbstractPaymentDriver
$invoices->first()->invitations->each(function ($invitation) use ($nmo){ $invoices->first()->invitations->each(function ($invitation) use ($nmo){
if ($invitation->contact->send_email && $invitation->contact->email) { if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) {
$nmo->to_user = $invitation->contact; $nmo->to_user = $invitation->contact;
NinjaMailerJob::dispatch($nmo); NinjaMailerJob::dispatch($nmo);

View File

@ -271,4 +271,68 @@ class ACH
$this->wepay_payment_driver->storeGatewayToken($data); $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);
}
}
} }

View File

@ -262,6 +262,7 @@ https://developer.wepay.com/api/api-calls/checkout
private function storePaymentMethod($response, $payment_method_id) private function storePaymentMethod($response, $payment_method_id)
{ {
nlog("storing card"); nlog("storing card");
$payment_meta = new \stdClass; $payment_meta = new \stdClass;
$payment_meta->exp_month = (string) $response->expiration_month; $payment_meta->exp_month = (string) $response->expiration_month;
$payment_meta->exp_year = (string) $response->expiration_year; $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);
}
}
} }

View File

@ -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) if($gateway_type == GatewayType::BANK_TRANSFER)
@ -48,6 +48,9 @@ trait WePayCommon
$this->wepay_payment_driver->client->company, $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)]); return redirect()->route('client.payments.show', ['payment' => $this->wepay_payment_driver->encodePrimaryKey($payment->id)]);
} }

View File

@ -119,14 +119,14 @@ class WePayPaymentDriver extends BaseDriver
$contact = $client->primary_contact()->first() ? $client->primary_contact()->first() : $lient->contacts->first(); $contact = $client->primary_contact()->first() ? $client->primary_contact()->first() : $lient->contacts->first();
$data['contact'] = $contact; $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) public function authorizeResponse($request)
{ {
$this->init(); $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) public function verificationView(ClientGatewayToken $cgt)
@ -147,19 +147,23 @@ class WePayPaymentDriver extends BaseDriver
{ {
$this->init(); $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) public function processPaymentResponse($request)
{ {
$this->init(); $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) 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) public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)

View File

@ -44,7 +44,7 @@ class SendEmail
} }
$this->credit->invitations->each(function ($invitation) { $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); $email_builder = (new CreditEmail())->build($invitation, $this->reminder_template);
// EmailCredit::dispatchNow($email_builder, $invitation, $invitation->company); // EmailCredit::dispatchNow($email_builder, $invitation, $invitation->company);

View File

@ -192,6 +192,8 @@ class InvoiceService
public function handleCancellation() public function handleCancellation()
{ {
$this->removeUnpaidGatewayFees();
$this->invoice = (new HandleCancellation($this->invoice))->run(); $this->invoice = (new HandleCancellation($this->invoice))->run();
return $this; return $this;
@ -199,6 +201,8 @@ class InvoiceService
public function markDeleted() public function markDeleted()
{ {
$this->removeUnpaidGatewayFees();
$this->invoice = (new MarkInvoiceDeleted($this->invoice))->run(); $this->invoice = (new MarkInvoiceDeleted($this->invoice))->run();
return $this; return $this;
@ -213,6 +217,8 @@ class InvoiceService
public function reverseCancellation() public function reverseCancellation()
{ {
$this->removeUnpaidGatewayFees();
$this->invoice = (new HandleCancellation($this->invoice))->reverse(); $this->invoice = (new HandleCancellation($this->invoice))->reverse();
return $this; return $this;
@ -278,11 +284,14 @@ class InvoiceService
public function updateStatus() 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(); // if ((int)$this->invoice->balance == 0) {
// InvoiceWorkflowSettings::dispatchNow($this->invoice);
} // $this->setStatus(Invoice::STATUS_PAID)->workFlow();
// }
if ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) { if ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) {
$this->setStatus(Invoice::STATUS_PARTIAL); $this->setStatus(Invoice::STATUS_PARTIAL);

View File

@ -26,6 +26,8 @@ class MarkInvoiceDeleted extends AbstractService
private $total_payments = 0; private $total_payments = 0;
private $balance_adjustment = 0;
public function __construct(Invoice $invoice) public function __construct(Invoice $invoice)
{ {
$this->invoice = $invoice; $this->invoice = $invoice;
@ -51,7 +53,7 @@ class MarkInvoiceDeleted extends AbstractService
private function adjustLedger() 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->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; return $this;
} }
@ -65,7 +67,7 @@ class MarkInvoiceDeleted extends AbstractService
private function adjustBalance() 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; 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'); //$this->total_payments = $this->invoice->payments->sum('amount - refunded');
nlog("adjustment amount = {$this->adjustment_amount}"); // nlog("adjustment amount = {$this->adjustment_amount}");
nlog("total payments = {$this->total_payments}"); // nlog("total payments = {$this->total_payments}");
return $this; return $this;
} }

View File

@ -44,7 +44,7 @@ class SendEmail extends AbstractService
} }
$this->invoice->invitations->each(function ($invitation) { $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); EmailEntity::dispatchNow($invitation, $invitation->company, $this->reminder_template);
} }
}); });

View File

@ -81,8 +81,14 @@ class RefundPayment
if ($response['success'] == false) { if ($response['success'] == false) {
$this->payment->save(); $this->payment->save();
if(array_key_exists('description', $response))
throw new PaymentRefundFailed($response['description']); throw new PaymentRefundFailed($response['description']);
else
throw new PaymentRefundFailed();
} }
} }
} else { } else {
$this->payment->refunded += $this->total_refund; $this->payment->refunded += $this->total_refund;

View File

@ -42,7 +42,7 @@ class SendEmail
} }
$this->quote->invitations->each(function ($invitation) { $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); EmailEntity::dispatchNow($invitation, $invitation->company, $this->reminder_template);
} }
}); });

View File

@ -47,8 +47,9 @@ trait Inviteable
{ {
$entity_type = Str::snake(class_basename($this->entityType())); $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(); $domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
}
else else
$domain = config('ninja.app_url'); $domain = config('ninja.app_url');

View File

@ -372,9 +372,12 @@ CREATE TABLE `companies` (
`expense_inclusive_taxes` tinyint(1) NOT NULL DEFAULT '0', `expense_inclusive_taxes` tinyint(1) NOT NULL DEFAULT '0',
`session_timeout` int(11) NOT NULL DEFAULT '0', `session_timeout` int(11) NOT NULL DEFAULT '0',
`oauth_password_required` tinyint(1) 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', `default_password_timeout` int(11) NOT NULL DEFAULT '30',
`show_task_end_date` tinyint(1) NOT NULL DEFAULT '0', `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`), PRIMARY KEY (`id`),
UNIQUE KEY `companies_company_key_unique` (`company_key`), UNIQUE KEY `companies_company_key_unique` (`company_key`),
KEY `companies_industry_id_foreign` (`industry_id`), 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 (81,'2021_06_24_115919_update_designs',2);
INSERT INTO `migrations` VALUES (82,'2021_07_08_115919_update_designs',3); 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 (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);

View File

@ -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 --}} {{-- Body --}}
{{ $support_message }} {{ $support_message }}

View File

@ -41,7 +41,7 @@
</span> </span>
</th> </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"> <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') }} {{ ctrans('texts.payment_type_id') }}
</span> </span>
</th> </th>

View File

@ -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/{company}', 'MigrationController@purgeCompany')->middleware('password_protected');
Route::post('companies/purge_save_settings/{company}', 'MigrationController@purgeCompanySaveSettings')->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::resource('companies', 'CompanyController'); // name = (companies. index / create / show / update / destroy / edit
Route::put('companies/{company}/upload', 'CompanyController@upload'); Route::put('companies/{company}/upload', 'CompanyController@upload');
Route::get('company_ledger', 'CompanyLedgerController@index')->name('company_ledger.index'); Route::get('company_ledger', 'CompanyLedgerController@index')->name('company_ledger.index');

View File

@ -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('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('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download')->middleware(['document_db']);
Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('client.error'); 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::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 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('credit/{invitation_key}/download_pdf', 'CreditController@downloadPdf')->name('credit.download_invitation_key');
Route::get('{entity}/{invitation_key}/download', 'ClientPortal\InvitationController@routerForDownload'); 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('{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'); Route::get('phantom/{entity}/{invitation_key}', '\App\Utils\PhantomJS\Phantom@displayInvitation')->middleware(['invite_db', 'phantom_secret'])->name('phantom_view');

View File

@ -54,11 +54,11 @@ class CancelInvoiceTest extends TestCase
$this->assertEquals(Invoice::STATUS_SENT, $this->invoice->status_id); $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(0, $this->invoice->fresh()->balance);
$this->assertEquals($this->client->fresh()->balance, ($client_balance - $invoice_balance)); $this->assertEquals($this->client->fresh()->balance, ($client_balance - $invoice_balance));
$this->assertNotEquals($client_balance, $this->client->fresh()->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);
} }
} }

View File

@ -11,6 +11,7 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\Http\Middleware\PasswordProtection;
use App\Models\Company; use App\Models\Company;
use App\Models\CompanyToken; use App\Models\CompanyToken;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
@ -47,6 +48,8 @@ class CompanyTest extends TestCase
public function testCompanyList() public function testCompanyList()
{ {
$this->withoutMiddleware(PasswordProtection::class);
$response = $this->withHeaders([ $response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'), 'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token, 'X-API-TOKEN' => $this->token,
@ -117,6 +120,7 @@ class CompanyTest extends TestCase
$response = $this->withHeaders([ $response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'), 'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token, 'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->delete('/api/v1/companies/'.$this->encodePrimaryKey($company->id)) ])->delete('/api/v1/companies/'.$this->encodePrimaryKey($company->id))
->assertStatus(200); ->assertStatus(200);
} }

View File

@ -14,6 +14,7 @@ use App\Factory\ClientFactory;
use App\Factory\CreditFactory; use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory; use App\Factory\InvoiceFactory;
use App\Helpers\Invoice\InvoiceSum; use App\Helpers\Invoice\InvoiceSum;
use App\Models\ClientContact;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
@ -63,6 +64,14 @@ class RefundTest extends TestCase
$client = ClientFactory::create($this->company->id, $this->user->id); $client = ClientFactory::create($this->company->id, $this->user->id);
$client->save(); $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 = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id
$this->invoice->client_id = $client->id; $this->invoice->client_id = $client->id;
$this->invoice->status_id = Invoice::STATUS_SENT; $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 = ClientFactory::create($this->company->id, $this->user->id);
$client->save(); $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 = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id
$this->invoice->client_id = $client->id; $this->invoice->client_id = $client->id;
$this->invoice->status_id = Invoice::STATUS_SENT; $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 = ClientFactory::create($this->company->id, $this->user->id);
$client->save(); $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 = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id
$this->invoice->client_id = $client->id; $this->invoice->client_id = $client->id;
$this->invoice->status_id = Invoice::STATUS_SENT; $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 = ClientFactory::create($this->company->id, $this->user->id);
$client->save(); $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 = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id
$this->invoice->client_id = $client->id; $this->invoice->client_id = $client->id;
$this->invoice->status_id = Invoice::STATUS_SENT; $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 = ClientFactory::create($this->company->id, $this->user->id);
$client->save(); $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 = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id
$this->invoice->client_id = $client->id; $this->invoice->client_id = $client->id;
$this->invoice->status_id = Invoice::STATUS_SENT; $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 = ClientFactory::create($this->company->id, $this->user->id);
$client->save(); $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 = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id
$this->invoice->client_id = $client->id; $this->invoice->client_id = $client->id;
$this->invoice->status_id = Invoice::STATUS_SENT; $this->invoice->status_id = Invoice::STATUS_SENT;

View File

@ -41,6 +41,27 @@ class NumberTest extends TestCase
$this->assertEquals(2.15, $rounded); $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() // public function testParsingFloats()
// { // {
// Currency::all()->each(function ($currency) { // Currency::all()->each(function ($currency) {