mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-08-30 23:20:34 -04:00
Merge remote-tracking branch 'origin/v5-develop' into v5-develop
# Conflicts: # resources/lang/en/texts.php
This commit is contained in:
commit
0d1596339e
@ -1 +1 @@
|
||||
5.3.19
|
||||
5.3.22
|
@ -23,6 +23,7 @@ use App\Jobs\Util\SchedulerCheck;
|
||||
use App\Jobs\Util\SendFailedEmails;
|
||||
use App\Jobs\Util\UpdateExchangeRates;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
@ -69,6 +70,10 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->job(new SchedulerCheck)->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->call(function () {
|
||||
Account::whereNotNull('id')->update(['is_scheduler_running' => true]);
|
||||
})->everyFiveMinutes()->withoutOverlapping();
|
||||
|
||||
/* Run hosted specific jobs */
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
|
@ -28,9 +28,9 @@ class CloneQuoteToInvoiceFactory
|
||||
unset($quote_array['invoice_id']);
|
||||
unset($quote_array['id']);
|
||||
unset($quote_array['invitations']);
|
||||
unset($quote_array['terms']);
|
||||
// unset($quote_array['public_notes']);
|
||||
unset($quote_array['footer']);
|
||||
//unset($quote_array['terms']);
|
||||
//unset($quote_array['public_notes']);
|
||||
//unset($quote_array['footer']);
|
||||
unset($quote_array['design_id']);
|
||||
|
||||
foreach ($quote_array as $key => $value) {
|
||||
|
@ -67,9 +67,10 @@ class ContactResetPasswordController extends Controller
|
||||
$account_id = $request->get('account_id');
|
||||
$account = Account::find($account_id);
|
||||
$db = $account->companies->first()->db;
|
||||
$company = $account->companies->first();
|
||||
|
||||
return $this->render('auth.passwords.reset')->with(
|
||||
['token' => $token, 'email' => $request->email, 'account' => $account, 'db' => $db]
|
||||
['token' => $token, 'email' => $request->email, 'account' => $account, 'db' => $db, 'company' => $company]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,10 @@ use App\Http\Controllers\Controller;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Payment;
|
||||
use App\Services\ClientPortal\InstantPayment;
|
||||
use App\Utils\CurlUtils;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -65,11 +68,17 @@ class InvitationController extends Controller
|
||||
private function genericRouter(string $entity, string $invitation_key)
|
||||
{
|
||||
|
||||
if(!in_array($entity, ['invoice', 'credit', 'quote', 'recurring_invoice']))
|
||||
return response()->json(['message' => 'Invalid resource request']);
|
||||
|
||||
$key = $entity.'_id';
|
||||
|
||||
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||
|
||||
$invitation = $entity_obj::whereRaw('BINARY `key`= ?', [$invitation_key])
|
||||
$invitation = $entity_obj::where('key', $invitation_key)
|
||||
->whereHas($entity, function ($query) {
|
||||
$query->where('is_deleted',0);
|
||||
})
|
||||
->with('contact.client')
|
||||
->firstOrFail();
|
||||
|
||||
@ -133,6 +142,9 @@ class InvitationController extends Controller
|
||||
private function returnRawPdf(string $entity, string $invitation_key)
|
||||
{
|
||||
|
||||
if(!in_array($entity, ['invoice', 'credit', 'quote', 'recurring_invoice']))
|
||||
return response()->json(['message' => 'Invalid resource request']);
|
||||
|
||||
$key = $entity.'_id';
|
||||
|
||||
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||
@ -181,4 +193,41 @@ class InvitationController extends Controller
|
||||
return redirect()->route('client.payments.show', $payment->hashed_id);
|
||||
|
||||
}
|
||||
|
||||
public function payInvoice(Request $request, string $invitation_key)
|
||||
{
|
||||
$invitation = InvoiceInvitation::where('key', $invitation_key)
|
||||
->with('contact.client')
|
||||
->firstOrFail();
|
||||
|
||||
auth()->guard('contact')->login($invitation->contact, true);
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if($invoice->partial > 0)
|
||||
$amount = round($invoice->partial, (int)$invoice->client->currency()->precision);
|
||||
else
|
||||
$amount = round($invoice->balance, (int)$invoice->client->currency()->precision);
|
||||
|
||||
$gateways = $invitation->contact->client->service()->getPaymentMethods($amount);
|
||||
|
||||
if(is_array($gateways))
|
||||
{
|
||||
|
||||
$data = [
|
||||
'company_gateway_id' => $gateways[0]['company_gateway_id'],
|
||||
'payment_method_id' => $gateways[0]['gateway_type_id'],
|
||||
'payable_invoices' => [
|
||||
['invoice_id' => $invitation->invoice->hashed_id, 'amount' => $amount],
|
||||
],
|
||||
'signature' => false
|
||||
];
|
||||
|
||||
$request->replace($data);
|
||||
|
||||
return (new InstantPayment($request))->run();
|
||||
}
|
||||
|
||||
abort(404, "Invoice not found");
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\Services\ClientPortal\InstantPayment;
|
||||
use App\Services\Subscription\SubscriptionService;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
@ -79,235 +80,7 @@ class PaymentController extends Controller
|
||||
*/
|
||||
public function process(Request $request)
|
||||
{
|
||||
$is_credit_payment = false;
|
||||
$tokens = [];
|
||||
|
||||
if ($request->input('company_gateway_id') == CompanyGateway::GATEWAY_CREDIT) {
|
||||
$is_credit_payment = true;
|
||||
}
|
||||
|
||||
$gateway = CompanyGateway::find($request->input('company_gateway_id'));
|
||||
|
||||
/**
|
||||
* find invoices
|
||||
*
|
||||
* ['invoice_id' => xxx, 'amount' => 22.00]
|
||||
*/
|
||||
|
||||
$payable_invoices = collect($request->payable_invoices);
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get();
|
||||
|
||||
$invoices->each(function($invoice){
|
||||
$invoice->service()->removeUnpaidGatewayFees()->save();
|
||||
});
|
||||
|
||||
/* pop non payable invoice from the $payable_invoices array */
|
||||
|
||||
$payable_invoices = $payable_invoices->filter(function ($payable_invoice) use ($invoices) {
|
||||
return $invoices->where('hashed_id', $payable_invoice['invoice_id'])->first()->isPayable();
|
||||
});
|
||||
|
||||
/*return early if no invoices*/
|
||||
|
||||
if ($payable_invoices->count() == 0) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with(['message' => 'No payable invoices selected.']);
|
||||
}
|
||||
|
||||
$settings = auth()->user()->client->getMergedSettings();
|
||||
|
||||
// nlog($settings);
|
||||
|
||||
/* This loop checks for under / over payments and returns the user if a check fails */
|
||||
|
||||
foreach ($payable_invoices as $payable_invoice) {
|
||||
|
||||
/*Match the payable invoice to the Model Invoice*/
|
||||
|
||||
$invoice = $invoices->first(function ($inv) use ($payable_invoice) {
|
||||
return $payable_invoice['invoice_id'] == $inv->hashed_id;
|
||||
});
|
||||
|
||||
/*
|
||||
* Check if company supports over & under payments.
|
||||
* Determine the payable amount and the max payable. ie either partial or invoice balance
|
||||
*/
|
||||
|
||||
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount']), auth()->user()->client->currency()->precision);
|
||||
$invoice_balance = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), auth()->user()->client->currency()->precision);
|
||||
|
||||
/*If we don't allow under/over payments force the payable amount - prevents inspect element adjustments in JS*/
|
||||
|
||||
if ($settings->client_portal_allow_under_payment == false && $settings->client_portal_allow_over_payment == false) {
|
||||
$payable_invoice['amount'] = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), auth()->user()->client->currency()->precision);
|
||||
}
|
||||
|
||||
if (!$settings->client_portal_allow_under_payment && $payable_amount < $invoice_balance) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]));
|
||||
}
|
||||
|
||||
if ($settings->client_portal_allow_under_payment) {
|
||||
if ($invoice_balance < $settings->client_portal_under_payment_minimum && $payable_amount < $invoice_balance) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]));
|
||||
}
|
||||
|
||||
if ($invoice_balance < $settings->client_portal_under_payment_minimum) {
|
||||
// Skip the under payment rule.
|
||||
}
|
||||
|
||||
if ($invoice_balance >= $settings->client_portal_under_payment_minimum && $payable_amount < $settings->client_portal_under_payment_minimum) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]));
|
||||
}
|
||||
}
|
||||
|
||||
/* If we don't allow over payments and the amount exceeds the balance */
|
||||
|
||||
if (!$settings->client_portal_allow_over_payment && $payable_amount > $invoice_balance) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.over_payments_disabled'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*Iterate through invoices and add gateway fees and other payment metadata*/
|
||||
|
||||
//$payable_invoices = $payable_invoices->map(function ($payable_invoice) use ($invoices, $settings) {
|
||||
$payable_invoice_collection = collect();
|
||||
|
||||
foreach ($payable_invoices as $payable_invoice) {
|
||||
// nlog($payable_invoice);
|
||||
|
||||
$payable_invoice['amount'] = Number::parseFloat($payable_invoice['amount']);
|
||||
|
||||
$invoice = $invoices->first(function ($inv) use ($payable_invoice) {
|
||||
return $payable_invoice['invoice_id'] == $inv->hashed_id;
|
||||
});
|
||||
|
||||
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount']), auth()->user()->client->currency()->precision);
|
||||
$invoice_balance = Number::roundValue($invoice->balance, auth()->user()->client->currency()->precision);
|
||||
|
||||
$payable_invoice['due_date'] = $this->formatDate($invoice->due_date, $invoice->client->date_format());
|
||||
$payable_invoice['invoice_number'] = $invoice->number;
|
||||
|
||||
if (isset($invoice->po_number)) {
|
||||
$additional_info = $invoice->po_number;
|
||||
} elseif (isset($invoice->public_notes)) {
|
||||
$additional_info = $invoice->public_notes;
|
||||
} else {
|
||||
$additional_info = $invoice->date;
|
||||
}
|
||||
|
||||
$payable_invoice['additional_info'] = $additional_info;
|
||||
|
||||
$payable_invoice_collection->push($payable_invoice);
|
||||
}
|
||||
//});
|
||||
|
||||
if (request()->has('signature') && !is_null(request()->signature) && !empty(request()->signature)) {
|
||||
$invoices->each(function ($invoice) use ($request) {
|
||||
InjectSignature::dispatch($invoice, $request->signature);
|
||||
});
|
||||
}
|
||||
|
||||
$payable_invoices = $payable_invoice_collection;
|
||||
|
||||
$payment_method_id = $request->input('payment_method_id');
|
||||
$invoice_totals = $payable_invoices->sum('amount');
|
||||
$first_invoice = $invoices->first();
|
||||
$credit_totals = $first_invoice->client->getSetting('use_credits_payment') == 'always' ? $first_invoice->client->service()->getCreditBalance() : 0;
|
||||
$starting_invoice_amount = $first_invoice->balance;
|
||||
|
||||
if ($gateway) {
|
||||
$first_invoice->service()->addGatewayFee($gateway, $payment_method_id, $invoice_totals)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gateway fee is calculated
|
||||
* by adding it as a line item, and then subtract
|
||||
* the starting and finishing amounts of the invoice.
|
||||
*/
|
||||
$fee_totals = $first_invoice->balance - $starting_invoice_amount;
|
||||
|
||||
if ($gateway) {
|
||||
$tokens = auth()->user()->client->gateway_tokens()
|
||||
->whereCompanyGatewayId($gateway->id)
|
||||
->whereGatewayTypeId($payment_method_id)
|
||||
->get();
|
||||
}
|
||||
|
||||
if(!$is_credit_payment){
|
||||
$credit_totals = 0;
|
||||
}
|
||||
|
||||
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals))];
|
||||
|
||||
if ($request->query('hash')) {
|
||||
$hash_data['billing_context'] = Cache::get($request->query('hash'));
|
||||
}
|
||||
|
||||
$payment_hash = new PaymentHash;
|
||||
$payment_hash->hash = Str::random(32);
|
||||
$payment_hash->data = $hash_data;
|
||||
$payment_hash->fee_total = $fee_totals;
|
||||
$payment_hash->fee_invoice_id = $first_invoice->id;
|
||||
|
||||
$payment_hash->save();
|
||||
|
||||
if($is_credit_payment){
|
||||
$amount_with_fee = max(0, (($invoice_totals + $fee_totals) - $credit_totals));
|
||||
}
|
||||
else{
|
||||
$credit_totals = 0;
|
||||
$amount_with_fee = max(0, $invoice_totals + $fee_totals);
|
||||
}
|
||||
|
||||
$totals = [
|
||||
'credit_totals' => $credit_totals,
|
||||
'invoice_totals' => $invoice_totals,
|
||||
'fee_total' => $fee_totals,
|
||||
'amount_with_fee' => $amount_with_fee,
|
||||
];
|
||||
|
||||
$data = [
|
||||
'payment_hash' => $payment_hash->hash,
|
||||
'total' => $totals,
|
||||
'invoices' => $payable_invoices,
|
||||
'tokens' => $tokens,
|
||||
'payment_method_id' => $payment_method_id,
|
||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||
];
|
||||
|
||||
if ($is_credit_payment || $totals <= 0) {
|
||||
return $this->processCreditPayment($request, $data);
|
||||
}
|
||||
|
||||
try {
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod($payment_method_id)
|
||||
->setPaymentHash($payment_hash)
|
||||
->checkRequirements()
|
||||
->processPaymentView($data);
|
||||
} catch (\Exception $e) {
|
||||
SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_ERROR,
|
||||
SystemLog::TYPE_FAILURE,
|
||||
auth('contact')->user()->client,
|
||||
auth('contact')->user()->client->company
|
||||
);
|
||||
|
||||
throw new PaymentFailed($e->getMessage());
|
||||
}
|
||||
return (new InstantPayment($request))->run();
|
||||
}
|
||||
|
||||
public function response(PaymentResponseRequest $request)
|
||||
|
@ -127,7 +127,7 @@ class EmailController extends BaseController
|
||||
|
||||
$entity_obj->invitations->each(function ($invitation) use ($data, $entity_string, $entity_obj, $template) {
|
||||
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->email) {
|
||||
|
||||
$entity_obj->service()->markSent()->save();
|
||||
|
||||
|
@ -234,21 +234,24 @@ class MigrationController extends BaseController
|
||||
public function startMigration(Request $request)
|
||||
{
|
||||
|
||||
// v4 Laravel 6
|
||||
|
||||
// $companies = [];
|
||||
|
||||
// foreach($request->all() as $input){
|
||||
|
||||
// if($input instanceof UploadedFile)
|
||||
// nlog('is file');
|
||||
// else
|
||||
// $companies[] = json_decode($input);
|
||||
// }
|
||||
|
||||
nlog("Starting Migration");
|
||||
|
||||
$companies = json_decode($request->companies,1);
|
||||
if($request->companies){
|
||||
//handle Laravel 5.5 UniHTTP
|
||||
$companies = json_decode($request->companies,1);
|
||||
}
|
||||
else {
|
||||
//handle Laravel 6 Guzzle
|
||||
$companies = [];
|
||||
|
||||
foreach($request->all() as $input){
|
||||
|
||||
if($input instanceof UploadedFile)
|
||||
nlog('is file');
|
||||
else
|
||||
$companies[] = json_decode($input,1);
|
||||
}
|
||||
}
|
||||
|
||||
if (app()->environment() === 'local') {
|
||||
nlog($request->all());
|
||||
@ -267,22 +270,11 @@ class MigrationController extends BaseController
|
||||
foreach($companies as $company)
|
||||
{
|
||||
|
||||
if(!is_array($company))
|
||||
continue;
|
||||
|
||||
$company = (array)$company;
|
||||
|
||||
// v4 Laravel 6
|
||||
// $input = $request->all();
|
||||
|
||||
// foreach ($input as $company) {
|
||||
|
||||
// if($company instanceof UploadedFile)
|
||||
// continue;
|
||||
// else
|
||||
// $company = json_decode($company,1);
|
||||
|
||||
// if (!$company || !is_int($company['company_index'] || !$request->file($company['company_index'])->isValid())) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
$company_count = $user->account->companies()->count();
|
||||
|
@ -216,7 +216,7 @@ class PreviewController extends BaseController
|
||||
if(!$request->has('entity_id'))
|
||||
$entity_obj->service()->fillDefaults()->save();
|
||||
|
||||
$entity_obj->load('client');
|
||||
$entity_obj->load('client.contacts','company');
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
@ -345,7 +345,7 @@ class PreviewController extends BaseController
|
||||
$invoice->setRelation('invitations', $invitation);
|
||||
$invoice->setRelation('client', $client);
|
||||
$invoice->setRelation('company', auth()->user()->company());
|
||||
$invoice->load('client');
|
||||
$invoice->load('client.company');
|
||||
|
||||
// nlog(print_r($invoice->toArray(),1));
|
||||
|
||||
|
@ -41,8 +41,9 @@ class CreditsTable extends Component
|
||||
->where('status_id', '<>', Credit::STATUS_DRAFT)
|
||||
->where('is_deleted', 0)
|
||||
->where(function ($query){
|
||||
$query->whereDate('due_date', '<=', now())
|
||||
->orWhereNull('due_date');
|
||||
$query->whereDate('due_date', '>=', now())
|
||||
->orWhereNull('due_date')
|
||||
->orWhere('due_date', '=', '');
|
||||
})
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->withTrashed()
|
||||
|
@ -52,7 +52,7 @@ class QueryLogging
|
||||
$timeEnd = microtime(true);
|
||||
$time = $timeEnd - $timeStart;
|
||||
|
||||
// nlog("Query count = {$count}");
|
||||
// info("Query count = {$count}");
|
||||
|
||||
if($count > 175){
|
||||
nlog("Query count = {$count}");
|
||||
|
@ -42,6 +42,9 @@ class SetInviteDb
|
||||
$entity = $request->route('entity');
|
||||
}
|
||||
|
||||
if($entity == "pay")
|
||||
$entity = "invoice";
|
||||
|
||||
if ($request->getSchemeAndHttpHost() && config('ninja.db.multi_db_enabled') && ! MultiDB::findAndSetDbByInvitation($entity, $request->route('invitation_key'))) {
|
||||
if (request()->json) {
|
||||
return response()->json($error, 403);
|
||||
|
@ -1331,6 +1331,12 @@ class CompanyImport implements ShouldQueue
|
||||
$new_obj->save(['timestamps' => false]);
|
||||
$new_obj->number = $this->getNextQuoteNumber($client = Client::find($obj_array['client_id']), $new_obj);
|
||||
}
|
||||
elseif($class == 'App\Models\ClientContact'){
|
||||
$new_obj = new ClientContact();
|
||||
$new_obj->company_id = $this->company->id;
|
||||
$new_obj->fill($obj_array);
|
||||
$new_obj->save(['timestamps' => false]);
|
||||
}
|
||||
else{
|
||||
$new_obj = $class::firstOrNew(
|
||||
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
|
||||
|
@ -50,6 +50,8 @@ class AutoBill
|
||||
MultiDB::setDb($this->db);
|
||||
|
||||
try{
|
||||
|
||||
nlog("autobill {$this->invoice->id}");
|
||||
|
||||
$this->invoice->service()->autoBill()->save();
|
||||
|
||||
|
@ -25,6 +25,8 @@ class AutoBillCron
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
private $counter = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -56,6 +58,7 @@ class AutoBillCron
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled',0);
|
||||
})
|
||||
->orderBy('id', 'DESC')
|
||||
->with('company');
|
||||
|
||||
nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill");
|
||||
@ -72,6 +75,7 @@ class AutoBillCron
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled',0);
|
||||
})
|
||||
->orderBy('id', 'DESC')
|
||||
->with('company');
|
||||
|
||||
nlog($auto_bill_invoices->count(). " full invoices to auto bill");
|
||||
@ -95,6 +99,7 @@ class AutoBillCron
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled',0);
|
||||
})
|
||||
->orderBy('id', 'DESC')
|
||||
->with('company');
|
||||
|
||||
nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill db = {$db}");
|
||||
@ -111,19 +116,23 @@ class AutoBillCron
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled',0);
|
||||
})
|
||||
->orderBy('id', 'DESC')
|
||||
->with('company');
|
||||
|
||||
nlog($auto_bill_invoices->count(). " full invoices to auto bill db = {$db}");
|
||||
|
||||
$auto_bill_invoices->cursor()->each(function ($invoice) use($db){
|
||||
|
||||
nlog($this->counter);
|
||||
AutoBill::dispatch($invoice, $db);
|
||||
$this->counter++;
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
nlog("fine");
|
||||
nlog("Auto Bill - fine");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,8 @@ class CreateEntityPdf implements ShouldQueue
|
||||
|
||||
public $entity_string = '';
|
||||
|
||||
public $client;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -69,15 +71,19 @@ class CreateEntityPdf implements ShouldQueue
|
||||
$this->invitation = $invitation;
|
||||
|
||||
if ($invitation instanceof InvoiceInvitation) {
|
||||
// $invitation->load('contact.client.company','invoice.client','invoice.user.account');
|
||||
$this->entity = $invitation->invoice;
|
||||
$this->entity_string = 'invoice';
|
||||
} elseif ($invitation instanceof QuoteInvitation) {
|
||||
// $invitation->load('contact.client.company','quote.client','quote.user.account');
|
||||
$this->entity = $invitation->quote;
|
||||
$this->entity_string = 'quote';
|
||||
} elseif ($invitation instanceof CreditInvitation) {
|
||||
// $invitation->load('contact.client.company','credit.client','credit.user.account');
|
||||
$this->entity = $invitation->credit;
|
||||
$this->entity_string = 'credit';
|
||||
} elseif ($invitation instanceof RecurringInvoiceInvitation) {
|
||||
// $invitation->load('contact.client.company','recurring_invoice');
|
||||
$this->entity = $invitation->recurring_invoice;
|
||||
$this->entity_string = 'recurring_invoice';
|
||||
}
|
||||
@ -86,6 +92,8 @@ class CreateEntityPdf implements ShouldQueue
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
$this->client = $invitation->contact->client;
|
||||
|
||||
$this->disk = Ninja::isHosted() ? config('filesystems.default') : $disk;
|
||||
|
||||
}
|
||||
@ -102,7 +110,7 @@ class CreateEntityPdf implements ShouldQueue
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
|
||||
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
|
||||
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->generate($this->invitation);
|
||||
@ -111,22 +119,22 @@ class CreateEntityPdf implements ShouldQueue
|
||||
$entity_design_id = '';
|
||||
|
||||
if ($this->entity instanceof Invoice) {
|
||||
$path = $this->entity->client->invoice_filepath($this->invitation);
|
||||
$path = $this->client->invoice_filepath($this->invitation);
|
||||
$entity_design_id = 'invoice_design_id';
|
||||
} elseif ($this->entity instanceof Quote) {
|
||||
$path = $this->entity->client->quote_filepath($this->invitation);
|
||||
$path = $this->client->quote_filepath($this->invitation);
|
||||
$entity_design_id = 'quote_design_id';
|
||||
} elseif ($this->entity instanceof Credit) {
|
||||
$path = $this->entity->client->credit_filepath($this->invitation);
|
||||
$path = $this->client->credit_filepath($this->invitation);
|
||||
$entity_design_id = 'credit_design_id';
|
||||
} elseif ($this->entity instanceof RecurringInvoice) {
|
||||
$path = $this->entity->client->recurring_invoice_filepath($this->invitation);
|
||||
$path = $this->client->recurring_invoice_filepath($this->invitation);
|
||||
$entity_design_id = 'invoice_design_id';
|
||||
}
|
||||
|
||||
$file_path = $path.$this->entity->numberFormatter().'.pdf';
|
||||
|
||||
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->entity->client->getSetting($entity_design_id));
|
||||
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->client->getSetting($entity_design_id));
|
||||
|
||||
// if(!$this->company->account->hasFeature(Account::FEATURE_DIFFERENT_DESIGNS))
|
||||
// $entity_design_id = 2;
|
||||
@ -152,18 +160,18 @@ class CreateEntityPdf implements ShouldQueue
|
||||
|
||||
$state = [
|
||||
'template' => $template->elements([
|
||||
'client' => $this->entity->client,
|
||||
'client' => $this->client,
|
||||
'entity' => $this->entity,
|
||||
'pdf_variables' => (array) $this->entity->company->settings->pdf_variables,
|
||||
'pdf_variables' => (array) $this->company->settings->pdf_variables,
|
||||
'$product' => $design->design->product,
|
||||
'variables' => $variables,
|
||||
]),
|
||||
'variables' => $variables,
|
||||
'options' => [
|
||||
'all_pages_header' => $this->entity->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'),
|
||||
'all_pages_header' => $this->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $this->client->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $this->entity->client->company->markdown_enabled,
|
||||
'process_markdown' => $this->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMakerService($state);
|
||||
|
@ -74,12 +74,15 @@ class EmailPayment implements ShouldQueue
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->payment->load('invoices');
|
||||
$this->contact->load('client');
|
||||
|
||||
$email_builder = (new PaymentEmailEngine($this->payment, $this->contact))->build();
|
||||
|
||||
$invitation = null;
|
||||
|
||||
if($this->payment->invoices()->exists())
|
||||
$invitation = $this->payment->invoices()->first()->invitations()->first();
|
||||
if($this->payment->invoices && $this->payment->invoices->count() >=1)
|
||||
$invitation = $this->payment->invoices->first()->invitations()->first();
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new TemplateEmail($email_builder, $this->contact, $invitation);
|
||||
|
@ -59,15 +59,15 @@ class SendRecurring implements ShouldQueue
|
||||
public function handle() : void
|
||||
{
|
||||
//reset all contacts here
|
||||
$this->recurring_invoice->client->contacts()->update(['send_email' => false]);
|
||||
// $this->recurring_invoice->client->contacts()->update(['send_email' => false]);
|
||||
|
||||
$this->recurring_invoice->invitations->each(function ($invitation){
|
||||
// $this->recurring_invoice->invitations->each(function ($invitation){
|
||||
|
||||
$contact = $invitation->contact;
|
||||
$contact->send_email = true;
|
||||
$contact->save();
|
||||
// $contact = $invitation->contact;
|
||||
// $contact->send_email = true;
|
||||
// $contact->save();
|
||||
|
||||
});
|
||||
// });
|
||||
|
||||
// Generate Standard Invoice
|
||||
$invoice = RecurringInvoiceToInvoiceFactory::create($this->recurring_invoice, $this->recurring_invoice->client);
|
||||
@ -153,7 +153,7 @@ class SendRecurring implements ShouldQueue
|
||||
}
|
||||
|
||||
//important catch all here - we should never leave contacts send_email to false incase they are permanently set to false in the future.
|
||||
$this->recurring_invoice->client->contacts()->update(['send_email' => true]);
|
||||
// $this->recurring_invoice->client->contacts()->update(['send_email' => true]);
|
||||
|
||||
}
|
||||
|
||||
|
@ -237,9 +237,12 @@ class Import implements ShouldQueue
|
||||
//company size check
|
||||
if ($this->company->invoices()->count() > 500 || $this->company->products()->count() > 500 || $this->company->clients()->count() > 500) {
|
||||
$this->company->is_large = true;
|
||||
$this->company->save();
|
||||
}
|
||||
|
||||
|
||||
$this->company->client_registration_fields = \App\DataMapper\ClientRegistrationFields::generate();
|
||||
$this->company->save();
|
||||
|
||||
$this->setInitialCompanyLedgerBalances();
|
||||
|
||||
// $this->fixClientBalances();
|
||||
|
@ -38,8 +38,6 @@ class SchedulerCheck implements ShouldQueue
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
Account::whereNotNull('id')->update(['is_scheduler_running' => true]);
|
||||
|
||||
if(config('ninja.app_version') != base_path('VERSION.txt'))
|
||||
{
|
||||
|
||||
|
@ -47,6 +47,7 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
$this->company = $payment->company;
|
||||
$this->client = $payment->client;
|
||||
$this->contact = $contact ?: $this->client->primary_contact()->first();
|
||||
$this->contact->load('client.company');
|
||||
$this->settings = $this->client->getMergedSettings();
|
||||
$this->template_data = $template_data;
|
||||
$this->helpers = new Helpers();
|
||||
|
@ -119,6 +119,10 @@ class Activity extends StaticModel
|
||||
'hashed_id',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
'backup',
|
||||
];
|
||||
|
||||
public function getHashedIdAttribute()
|
||||
{
|
||||
return $this->encodePrimaryKey($this->id);
|
||||
|
@ -84,12 +84,12 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
'gateway_tokens',
|
||||
'documents',
|
||||
'contacts.company',
|
||||
// 'gateway_tokens',
|
||||
// 'documents',
|
||||
// 'contacts.company',
|
||||
// 'currency',
|
||||
// 'primary_contact',
|
||||
'country',
|
||||
// 'country',
|
||||
// 'contacts',
|
||||
// 'shipping_country',
|
||||
// 'company',
|
||||
|
@ -93,6 +93,7 @@ class Gateway extends StaticModel
|
||||
GatewayType::BANK_TRANSFER => ['refund' => false, 'token_billing' => true],
|
||||
GatewayType::KBC => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::BANCONTACT => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::IDEAL => ['refund' => false, 'token_billing' => false],
|
||||
];
|
||||
case 15:
|
||||
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal
|
||||
|
@ -27,6 +27,7 @@ class GatewayType extends StaticModel
|
||||
const CREDIT = 10;
|
||||
const KBC = 11;
|
||||
const BANCONTACT = 12;
|
||||
const IDEAL = 13;
|
||||
|
||||
public function gateway()
|
||||
{
|
||||
@ -63,7 +64,8 @@ class GatewayType extends StaticModel
|
||||
return ctrans('texts.kbc_cbc');
|
||||
case self::BANCONTACT:
|
||||
return ctrans('texts.bancontact');
|
||||
|
||||
case self::IDEAL:
|
||||
return ctrans('texts.ideal');
|
||||
default:
|
||||
return 'Undefined.';
|
||||
break;
|
||||
|
@ -45,6 +45,7 @@ class PaymentType extends StaticModel
|
||||
const MOLLIE_BANK_TRANSFER = 34;
|
||||
const KBC = 35;
|
||||
const BANCONTACT = 36;
|
||||
const IDEAL = 37;
|
||||
|
||||
public static function parseCardType($cardName)
|
||||
{
|
||||
|
@ -449,6 +449,10 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
public function calculateDueDate($date)
|
||||
{
|
||||
//if nothing is set, assume we are using terms.
|
||||
if(!$this->due_date_days)
|
||||
return $this->calculateDateFromTerms($date);
|
||||
|
||||
switch ($this->due_date_days) {
|
||||
case 'terms':
|
||||
return $this->calculateDateFromTerms($date);
|
||||
|
@ -64,6 +64,10 @@ class Subscription extends BaseModel
|
||||
'deleted_at' => 'timestamp',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
'company',
|
||||
];
|
||||
|
||||
public function service(): SubscriptionService
|
||||
{
|
||||
return new SubscriptionService($this);
|
||||
|
@ -34,9 +34,11 @@ class InvoiceObserver
|
||||
->where('event_id', Webhook::EVENT_CREATE_INVOICE)
|
||||
->exists();
|
||||
|
||||
$invoice->load('client');
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
$invoice->load('client');
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_INVOICE, $invoice, $invoice->company);
|
||||
}
|
||||
}
|
||||
@ -53,11 +55,14 @@ class InvoiceObserver
|
||||
->where('event_id', Webhook::EVENT_UPDATE_INVOICE)
|
||||
->exists();
|
||||
|
||||
$invoice->load('client');
|
||||
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
$invoice->load('client');
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -75,6 +80,9 @@ class InvoiceObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
$invoice->load('client');
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_INVOICE, $invoice, $invoice->company);
|
||||
}
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ class QuoteObserver
|
||||
->where('event_id', Webhook::EVENT_CREATE_QUOTE)
|
||||
->exists();
|
||||
|
||||
$quote->load('client');
|
||||
|
||||
if ($subscriptions) {
|
||||
$quote->load('client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_QUOTE, $quote, $quote->company);
|
||||
}
|
||||
}
|
||||
@ -49,10 +49,10 @@ class QuoteObserver
|
||||
->where('event_id', Webhook::EVENT_UPDATE_QUOTE)
|
||||
->exists();
|
||||
|
||||
$quote->load('client');
|
||||
|
||||
|
||||
if ($subscriptions) {
|
||||
$quote->load('client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_QUOTE, $quote, $quote->company);
|
||||
}
|
||||
|
||||
@ -71,6 +71,7 @@ class QuoteObserver
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
$quote->load('client');
|
||||
WebhookHandler::dispatch(Webhook::EVENT_DELETE_QUOTE, $quote, $quote->company);
|
||||
}
|
||||
}
|
||||
|
@ -395,7 +395,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
$invoices->first()->invitations->each(function ($invitation) use ($nmo) {
|
||||
|
||||
if ($invitation->contact->send_email && $invitation->contact->email) {
|
||||
if ($invitation->contact->email) {
|
||||
|
||||
$nmo->to_user = $invitation->contact;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
@ -459,7 +459,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
$invoices->first()->invitations->each(function ($invitation) use ($nmo){
|
||||
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->email) {
|
||||
|
||||
$nmo->to_user = $invitation->contact;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
@ -492,84 +492,84 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
public function checkRequirements()
|
||||
{
|
||||
if ($this->company_gateway->require_billing_address) {
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->address1)) {
|
||||
if ($this->checkRequiredResource($this->client->address1)) {
|
||||
$this->required_fields[] = 'billing_address1';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->address2)) {
|
||||
if ($this->checkRequiredResource($this->client->address2)) {
|
||||
$this->required_fields[] = 'billing_address2';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->city)) {
|
||||
if ($this->checkRequiredResource($this->client->city)) {
|
||||
$this->required_fields[] = 'billing_city';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->state)) {
|
||||
if ($this->checkRequiredResource($this->client->state)) {
|
||||
$this->required_fields[] = 'billing_state';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->postal_code)) {
|
||||
if ($this->checkRequiredResource($this->client->postal_code)) {
|
||||
$this->required_fields[] = 'billing_postal_code';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->country_id)) {
|
||||
if ($this->checkRequiredResource($this->client->country_id)) {
|
||||
$this->required_fields[] = 'billing_country';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_shipping_address) {
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->shipping_address1)) {
|
||||
if ($this->checkRequiredResource($this->client->shipping_address1)) {
|
||||
$this->required_fields[] = 'shipping_address1';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->shipping_address2)) {
|
||||
if ($this->checkRequiredResource($this->client->shipping_address2)) {
|
||||
$this->required_fields[] = 'shipping_address2';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->shipping_city)) {
|
||||
if ($this->checkRequiredResource($this->client->shipping_city)) {
|
||||
$this->required_fields[] = 'shipping_city';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->shipping_state)) {
|
||||
if ($this->checkRequiredResource($this->client->shipping_state)) {
|
||||
$this->required_fields[] = 'shipping_state';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->shipping_postal_code)) {
|
||||
if ($this->checkRequiredResource($this->client->shipping_postal_code)) {
|
||||
$this->required_fields[] = 'shipping_postal_code';
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->shipping_country_id)) {
|
||||
if ($this->checkRequiredResource($this->client->shipping_country_id)) {
|
||||
$this->required_fields[] = 'shipping_country';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_client_name) {
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->name)) {
|
||||
if ($this->checkRequiredResource($this->client->name)) {
|
||||
$this->required_fields[] = 'name';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_client_phone) {
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->phone)) {
|
||||
if ($this->checkRequiredResource($this->client->phone)) {
|
||||
$this->required_fields[] = 'phone';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_contact_email) {
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->email)) {
|
||||
if ($this->checkRequiredResource($this->email)) {
|
||||
$this->required_fields[] = 'contact_email';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_contact_name) {
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->first_name)) {
|
||||
$this->required_fields[] = 'contact_first_name';
|
||||
}
|
||||
// if ($this->company_gateway->require_contact_name) {
|
||||
// if ($this->checkRequiredResource($this->first_name)) {
|
||||
// $this->required_fields[] = 'contact_first_name';
|
||||
// }
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->last_name)) {
|
||||
$this->required_fields[] = 'contact_last_name';
|
||||
}
|
||||
}
|
||||
// if ($this->checkRequiredResource($this->last_name)) {
|
||||
// $this->required_fields[] = 'contact_last_name';
|
||||
// }
|
||||
// }
|
||||
|
||||
if ($this->company_gateway->require_postal_code) {
|
||||
// In case "require_postal_code" is true, we don't need billing address.
|
||||
@ -580,7 +580,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->checkRequiredResource(auth()->user('contact')->client->postal_code)) {
|
||||
if ($this->checkRequiredResource($this->client->postal_code)) {
|
||||
$this->required_fields[] = 'postal_code';
|
||||
}
|
||||
}
|
||||
|
215
app/PaymentDrivers/Mollie/IDEAL.php
Normal file
215
app/PaymentDrivers/Mollie/IDEAL.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Mollie;
|
||||
|
||||
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\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\PaymentDrivers\MolliePaymentDriver;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class IDEAL implements MethodInterface
|
||||
{
|
||||
protected MolliePaymentDriver $mollie;
|
||||
|
||||
public function __construct(MolliePaymentDriver $mollie)
|
||||
{
|
||||
$this->mollie = $mollie;
|
||||
|
||||
$this->mollie->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the authorization page for iDEAL.
|
||||
*
|
||||
* @param array $data
|
||||
* @return View
|
||||
*/
|
||||
public function authorizeView(array $data): View
|
||||
{
|
||||
return render('gateways.mollie.ideal.authorize', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the authorization for iDEAL.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function authorizeResponse(Request $request): RedirectResponse
|
||||
{
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the payment page for iDEAL.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$this->mollie->payment_hash
|
||||
->withData('gateway_type_id', GatewayType::IDEAL)
|
||||
->withData('client_id', $this->mollie->client->id);
|
||||
|
||||
try {
|
||||
$payment = $this->mollie->gateway->payments->create([
|
||||
'method' => 'ideal',
|
||||
'amount' => [
|
||||
'currency' => $this->mollie->client->currency()->code,
|
||||
'value' => $this->mollie->convertToMollieAmount((float) $this->mollie->payment_hash->data->amount_with_fee),
|
||||
],
|
||||
'description' => \sprintf('Invoices: %s', collect($data['invoices'])->pluck('invoice_number')),
|
||||
'redirectUrl' => route('client.payments.response', [
|
||||
'company_gateway_id' => $this->mollie->company_gateway->id,
|
||||
'payment_hash' => $this->mollie->payment_hash->hash,
|
||||
'payment_method_id' => GatewayType::IDEAL,
|
||||
]),
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->mollie->payment_hash->withData('payment_id', $payment->id);
|
||||
|
||||
return redirect(
|
||||
$payment->getCheckoutUrl()
|
||||
);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) {
|
||||
return $this->processUnsuccessfulPayment($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unsuccessful payment.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @throws PaymentFailed
|
||||
* @return void
|
||||
*/
|
||||
public function processUnsuccessfulPayment(\Exception $exception): void
|
||||
{
|
||||
PaymentFailureMailer::dispatch(
|
||||
$this->mollie->client,
|
||||
$exception->getMessage(),
|
||||
$this->mollie->client->company,
|
||||
$this->mollie->payment_hash->data->amount_with_fee
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$exception->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($exception->getMessage(), $exception->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the payments for the iDEAL.
|
||||
*
|
||||
* @param PaymentResponseRequest $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
if (!\property_exists($this->mollie->payment_hash->data, 'payment_id')) {
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed('Whoops, something went wrong. Missing required [payment_id] parameter. Please contact administrator. Reference hash: ' . $this->mollie->payment_hash->hash)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$payment = $this->mollie->gateway->payments->get(
|
||||
$this->mollie->payment_hash->data->payment_id
|
||||
);
|
||||
|
||||
if ($payment->status === 'paid') {
|
||||
return $this->processSuccessfulPayment($payment);
|
||||
}
|
||||
|
||||
if ($payment->status === 'open') {
|
||||
return $this->processOpenPayment($payment);
|
||||
}
|
||||
|
||||
if ($payment->status === 'failed') {
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed(ctrans('texts.status_failed'))
|
||||
);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed(ctrans('texts.status_voided'))
|
||||
);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) {
|
||||
return $this->processUnsuccessfulPayment($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the successful payment for iDEAL.
|
||||
*
|
||||
* @param string $status
|
||||
* @param ResourcesPayment $payment
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment, string $status = 'paid'): RedirectResponse
|
||||
{
|
||||
$data = [
|
||||
'gateway_type_id' => GatewayType::IDEAL,
|
||||
'amount' => array_sum(array_column($this->mollie->payment_hash->invoices(), 'amount')) + $this->mollie->payment_hash->fee_total,
|
||||
'payment_type' => PaymentType::IDEAL,
|
||||
'transaction_reference' => $payment->id,
|
||||
];
|
||||
|
||||
$payment_record = $this->mollie->createPayment(
|
||||
$data,
|
||||
$status === 'paid' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->mollie->encodePrimaryKey($payment_record->id)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle 'open' payment status for IDEAL.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processOpenPayment(\Mollie\Api\Resources\Payment $payment): RedirectResponse
|
||||
{
|
||||
return $this->processSuccessfulPayment($payment, 'open');
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Mollie\Bancontact;
|
||||
use App\PaymentDrivers\Mollie\BankTransfer;
|
||||
use App\PaymentDrivers\Mollie\CreditCard;
|
||||
use App\PaymentDrivers\Mollie\IDEAL;
|
||||
use App\PaymentDrivers\Mollie\KBC;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
@ -70,6 +71,7 @@ class MolliePaymentDriver extends BaseDriver
|
||||
GatewayType::BANCONTACT => Bancontact::class,
|
||||
GatewayType::BANK_TRANSFER => BankTransfer::class,
|
||||
GatewayType::KBC => KBC::class,
|
||||
GatewayType::IDEAL => IDEAL::class,
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_MOLLIE;
|
||||
@ -93,6 +95,7 @@ class MolliePaymentDriver extends BaseDriver
|
||||
$types[] = GatewayType::BANCONTACT;
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
$types[] = GatewayType::KBC;
|
||||
$types[] = GatewayType::IDEAL;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
@ -70,13 +70,9 @@ class ActivityRepository extends BaseRepository
|
||||
*/
|
||||
public function createBackup($entity, $activity)
|
||||
{
|
||||
|
||||
if($entity instanceof User){
|
||||
|
||||
}
|
||||
else if ($entity->company->is_disabled) {
|
||||
if ($entity instanceof User || $entity->company->is_disabled)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$backup = new Backup();
|
||||
|
||||
@ -85,6 +81,7 @@ class ActivityRepository extends BaseRepository
|
||||
|| get_class($entity) == Credit::class
|
||||
|| get_class($entity) == RecurringInvoice::class
|
||||
) {
|
||||
$entity->load('company', 'client');
|
||||
$contact = $entity->client->primary_contact()->first();
|
||||
$backup->html_backup = $this->generateHtml($entity);
|
||||
$backup->amount = $entity->amount;
|
||||
@ -92,7 +89,6 @@ class ActivityRepository extends BaseRepository
|
||||
|
||||
$backup->activity_id = $activity->id;
|
||||
$backup->json_backup = '';
|
||||
//$backup->json_backup = $entity->toJson();
|
||||
$backup->save();
|
||||
}
|
||||
|
||||
@ -121,6 +117,8 @@ class ActivityRepository extends BaseRepository
|
||||
$entity_design_id = 'credit_design_id';
|
||||
}
|
||||
|
||||
$entity->load('client','client.company');
|
||||
|
||||
$entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->client->getSetting($entity_design_id));
|
||||
|
||||
$design = Design::find($entity_design_id);
|
||||
|
@ -174,10 +174,6 @@ class BaseRepository
|
||||
if(array_key_exists('client_id', $data))
|
||||
$model->client_id = $data['client_id'];
|
||||
|
||||
//pickup changes here to recalculate reminders
|
||||
//if($model instanceof Invoice && ($model->isDirty('date') || $model->isDirty('due_date')))
|
||||
// $model->service()->setReminder()->save();
|
||||
|
||||
$client = Client::where('id', $model->client_id)->withTrashed()->first();
|
||||
|
||||
$state = [];
|
||||
@ -210,7 +206,10 @@ class BaseRepository
|
||||
$model->custom_surcharge_tax3 = $client->company->custom_surcharge_taxes3;
|
||||
$model->custom_surcharge_tax4 = $client->company->custom_surcharge_taxes4;
|
||||
|
||||
$model->saveQuietly();
|
||||
if(!$model->id)
|
||||
$model->save();
|
||||
else
|
||||
$model->saveQuietly();
|
||||
|
||||
/* Model now persisted, now lets do some child tasks */
|
||||
|
||||
@ -309,10 +308,6 @@ class BaseRepository
|
||||
|
||||
/* Perform model specific tasks */
|
||||
if ($model instanceof Invoice) {
|
||||
|
||||
nlog("Finished amount = " . $state['finished_amount']);
|
||||
nlog("Starting amount = " . $state['starting_amount']);
|
||||
nlog("Diff = " . ($state['finished_amount'] - $state['starting_amount']));
|
||||
|
||||
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
|
||||
|
||||
|
291
app/Services/ClientPortal/InstantPayment.php
Normal file
291
app/Services/ClientPortal/InstantPayment.php
Normal file
@ -0,0 +1,291 @@
|
||||
<?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\Services\ClientPortal;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Jobs\Invoice\InjectSignature;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\Services\Subscription\SubscriptionService;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
|
||||
class InstantPayment
|
||||
{
|
||||
use MakesHash;
|
||||
use MakesDates;
|
||||
|
||||
public Request $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
$is_credit_payment = false;
|
||||
|
||||
$tokens = [];
|
||||
|
||||
if ($this->request->input('company_gateway_id') == CompanyGateway::GATEWAY_CREDIT) {
|
||||
$is_credit_payment = true;
|
||||
}
|
||||
|
||||
$gateway = CompanyGateway::find($this->request->input('company_gateway_id'));
|
||||
|
||||
/**
|
||||
* find invoices
|
||||
*
|
||||
* ['invoice_id' => xxx, 'amount' => 22.00]
|
||||
*/
|
||||
|
||||
$payable_invoices = collect($this->request->payable_invoices);
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get();
|
||||
|
||||
$invoices->each(function($invoice){
|
||||
$invoice->service()->removeUnpaidGatewayFees()->save();
|
||||
});
|
||||
|
||||
/* pop non payable invoice from the $payable_invoices array */
|
||||
|
||||
$payable_invoices = $payable_invoices->filter(function ($payable_invoice) use ($invoices) {
|
||||
return $invoices->where('hashed_id', $payable_invoice['invoice_id'])->first()->isPayable();
|
||||
});
|
||||
|
||||
/*return early if no invoices*/
|
||||
|
||||
if ($payable_invoices->count() == 0) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with(['message' => 'No payable invoices selected.']);
|
||||
}
|
||||
|
||||
$client = $invoices->first()->client;
|
||||
$settings = $client->getMergedSettings();
|
||||
|
||||
/* This loop checks for under / over payments and returns the user if a check fails */
|
||||
|
||||
foreach ($payable_invoices as $payable_invoice) {
|
||||
|
||||
/*Match the payable invoice to the Model Invoice*/
|
||||
|
||||
$invoice = $invoices->first(function ($inv) use ($payable_invoice) {
|
||||
return $payable_invoice['invoice_id'] == $inv->hashed_id;
|
||||
});
|
||||
|
||||
/*
|
||||
* Check if company supports over & under payments.
|
||||
* Determine the payable amount and the max payable. ie either partial or invoice balance
|
||||
*/
|
||||
|
||||
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount'], $client->currency()->precision));
|
||||
$invoice_balance = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision);
|
||||
|
||||
/*If we don't allow under/over payments force the payable amount - prevents inspect element adjustments in JS*/
|
||||
|
||||
if ($settings->client_portal_allow_under_payment == false && $settings->client_portal_allow_over_payment == false) {
|
||||
$payable_invoice['amount'] = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision);
|
||||
}
|
||||
|
||||
if (!$settings->client_portal_allow_under_payment && $payable_amount < $invoice_balance) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]));
|
||||
}
|
||||
|
||||
if ($settings->client_portal_allow_under_payment) {
|
||||
if ($invoice_balance < $settings->client_portal_under_payment_minimum && $payable_amount < $invoice_balance) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]));
|
||||
}
|
||||
|
||||
if ($invoice_balance < $settings->client_portal_under_payment_minimum) {
|
||||
// Skip the under payment rule.
|
||||
}
|
||||
|
||||
if ($invoice_balance >= $settings->client_portal_under_payment_minimum && $payable_amount < $settings->client_portal_under_payment_minimum) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]));
|
||||
}
|
||||
}
|
||||
|
||||
/* If we don't allow over payments and the amount exceeds the balance */
|
||||
|
||||
if (!$settings->client_portal_allow_over_payment && $payable_amount > $invoice_balance) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.over_payments_disabled'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*Iterate through invoices and add gateway fees and other payment metadata*/
|
||||
|
||||
//$payable_invoices = $payable_invoices->map(function ($payable_invoice) use ($invoices, $settings) {
|
||||
$payable_invoice_collection = collect();
|
||||
|
||||
foreach ($payable_invoices as $payable_invoice) {
|
||||
// nlog($payable_invoice);
|
||||
|
||||
$payable_invoice['amount'] = Number::parseFloat($payable_invoice['amount']);
|
||||
|
||||
$invoice = $invoices->first(function ($inv) use ($payable_invoice) {
|
||||
return $payable_invoice['invoice_id'] == $inv->hashed_id;
|
||||
});
|
||||
|
||||
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount'], $client->currency()->precision));
|
||||
$invoice_balance = Number::roundValue($invoice->balance, $client->currency()->precision);
|
||||
|
||||
$payable_invoice['due_date'] = $this->formatDate($invoice->due_date, $invoice->client->date_format());
|
||||
$payable_invoice['invoice_number'] = $invoice->number;
|
||||
|
||||
if (isset($invoice->po_number)) {
|
||||
$additional_info = $invoice->po_number;
|
||||
} elseif (isset($invoice->public_notes)) {
|
||||
$additional_info = $invoice->public_notes;
|
||||
} else {
|
||||
$additional_info = $invoice->date;
|
||||
}
|
||||
|
||||
$payable_invoice['additional_info'] = $additional_info;
|
||||
|
||||
$payable_invoice_collection->push($payable_invoice);
|
||||
}
|
||||
|
||||
|
||||
if ($this->request->has('signature') && !is_null($this->request->signature) && !empty($this->request->signature)) {
|
||||
$invoices->each(function ($invoice){
|
||||
InjectSignature::dispatch($invoice, $this->request->signature);
|
||||
});
|
||||
}
|
||||
|
||||
$payable_invoices = $payable_invoice_collection;
|
||||
|
||||
$payment_method_id = $this->request->input('payment_method_id');
|
||||
$invoice_totals = $payable_invoices->sum('amount');
|
||||
$first_invoice = $invoices->first();
|
||||
$credit_totals = $first_invoice->client->getSetting('use_credits_payment') == 'always' ? $first_invoice->client->service()->getCreditBalance() : 0;
|
||||
$starting_invoice_amount = $first_invoice->balance;
|
||||
|
||||
if ($gateway) {
|
||||
$first_invoice->service()->addGatewayFee($gateway, $payment_method_id, $invoice_totals)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gateway fee is calculated
|
||||
* by adding it as a line item, and then subtract
|
||||
* the starting and finishing amounts of the invoice.
|
||||
*/
|
||||
$fee_totals = $first_invoice->balance - $starting_invoice_amount;
|
||||
|
||||
if ($gateway) {
|
||||
$tokens = $client->gateway_tokens()
|
||||
->whereCompanyGatewayId($gateway->id)
|
||||
->whereGatewayTypeId($payment_method_id)
|
||||
->get();
|
||||
}
|
||||
|
||||
if(!$is_credit_payment){
|
||||
$credit_totals = 0;
|
||||
}
|
||||
|
||||
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals))];
|
||||
|
||||
if ($this->request->query('hash')) {
|
||||
$hash_data['billing_context'] = Cache::get($this->request->query('hash'));
|
||||
}
|
||||
|
||||
$payment_hash = new PaymentHash;
|
||||
$payment_hash->hash = Str::random(32);
|
||||
$payment_hash->data = $hash_data;
|
||||
$payment_hash->fee_total = $fee_totals;
|
||||
$payment_hash->fee_invoice_id = $first_invoice->id;
|
||||
|
||||
$payment_hash->save();
|
||||
|
||||
if($is_credit_payment){
|
||||
$amount_with_fee = max(0, (($invoice_totals + $fee_totals) - $credit_totals));
|
||||
}
|
||||
else{
|
||||
$credit_totals = 0;
|
||||
$amount_with_fee = max(0, $invoice_totals + $fee_totals);
|
||||
}
|
||||
|
||||
$totals = [
|
||||
'credit_totals' => $credit_totals,
|
||||
'invoice_totals' => $invoice_totals,
|
||||
'fee_total' => $fee_totals,
|
||||
'amount_with_fee' => $amount_with_fee,
|
||||
];
|
||||
|
||||
$data = [
|
||||
'payment_hash' => $payment_hash->hash,
|
||||
'total' => $totals,
|
||||
'invoices' => $payable_invoices,
|
||||
'tokens' => $tokens,
|
||||
'payment_method_id' => $payment_method_id,
|
||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||
];
|
||||
|
||||
if ($is_credit_payment || $totals <= 0) {
|
||||
return $this->processCreditPayment($this->request, $data);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
return $gateway
|
||||
->driver($client)
|
||||
->setPaymentMethod($payment_method_id)
|
||||
->setPaymentHash($payment_hash)
|
||||
->checkRequirements()
|
||||
->processPaymentView($data);
|
||||
} catch (\Exception $e) {
|
||||
SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_ERROR,
|
||||
SystemLog::TYPE_FAILURE,
|
||||
$client,
|
||||
$client->company
|
||||
);
|
||||
|
||||
throw new PaymentFailed($e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function processCreditPayment(Request $request, array $data)
|
||||
{
|
||||
return render('gateways.credit.index', $data);
|
||||
}
|
||||
|
||||
}
|
@ -44,7 +44,7 @@ class SendEmail
|
||||
}
|
||||
|
||||
$this->credit->invitations->each(function ($invitation) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->email) {
|
||||
$email_builder = (new CreditEmail())->build($invitation, $this->reminder_template);
|
||||
|
||||
// EmailCredit::dispatchNow($email_builder, $invitation, $invitation->company);
|
||||
|
@ -304,7 +304,7 @@ class AutoBillInvoice extends AbstractService
|
||||
{
|
||||
|
||||
//get all client gateway tokens and set the is_default one to the first record
|
||||
$gateway_tokens = $this->client->gateway_tokens()->orderBy('is_default', 'DESC');
|
||||
$gateway_tokens = $this->client->gateway_tokens()->orderBy('is_default', 'DESC')->get();
|
||||
// $gateway_tokens = $this->client->gateway_tokens;
|
||||
|
||||
$filtered_gateways = $gateway_tokens->filter(function ($gateway_token) use($amount) {
|
||||
@ -312,7 +312,7 @@ class AutoBillInvoice extends AbstractService
|
||||
$company_gateway = $gateway_token->gateway;
|
||||
|
||||
//check if fees and limits are set
|
||||
if (isset($company_gateway->fees_and_limits) && property_exists($company_gateway->fees_and_limits, $gateway_token->gateway_type_id))
|
||||
if (isset($company_gateway->fees_and_limits) && !is_array($company_gateway->fees_and_limits) && property_exists($company_gateway->fees_and_limits, $gateway_token->gateway_type_id))
|
||||
{
|
||||
//if valid we keep this gateway_token
|
||||
if ($this->invoice->client->validGatewayForAmount($company_gateway->fees_and_limits->{$gateway_token->gateway_type_id}, $amount))
|
||||
|
@ -322,6 +322,8 @@ class InvoiceService
|
||||
|
||||
public function deletePdf()
|
||||
{
|
||||
$this->invoice->load('invitations');
|
||||
|
||||
$this->invoice->invitations->each(function ($invitation){
|
||||
|
||||
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
|
||||
@ -452,13 +454,13 @@ class InvoiceService
|
||||
if (! $this->invoice->design_id)
|
||||
$this->invoice->design_id = $this->decodePrimaryKey($settings->invoice_design_id);
|
||||
|
||||
if (!isset($this->invoice->footer))
|
||||
if (!isset($this->invoice->footer) || empty($this->invoice->footer))
|
||||
$this->invoice->footer = $settings->invoice_footer;
|
||||
|
||||
if (!isset($this->invoice->terms))
|
||||
if (!isset($this->invoice->terms) || empty($this->invoice->terms))
|
||||
$this->invoice->terms = $settings->invoice_terms;
|
||||
|
||||
if (!isset($this->invoice->public_notes))
|
||||
if (!isset($this->invoice->public_notes) || empty($this->invoice->public_notes))
|
||||
$this->invoice->public_notes = $this->invoice->client->public_notes;
|
||||
|
||||
/* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
|
||||
@ -473,8 +475,10 @@ class InvoiceService
|
||||
|
||||
if ($this->invoice->status_id == Invoice::STATUS_PAID && $this->invoice->client->getSetting('auto_archive_invoice')) {
|
||||
/* Throws: Payment amount xxx does not match invoice totals. */
|
||||
|
||||
$base_repository = new BaseRepository();
|
||||
$base_repository->archive($this->invoice);
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -44,7 +44,7 @@ class SendEmail extends AbstractService
|
||||
}
|
||||
|
||||
$this->invoice->invitations->each(function ($invitation) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->email) {
|
||||
EmailEntity::dispatchNow($invitation, $invitation->company, $this->reminder_template);
|
||||
}
|
||||
});
|
||||
|
@ -32,8 +32,10 @@ class SendEmail
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->payment->load('company', 'client.contacts');
|
||||
|
||||
$this->payment->client->contacts->each(function ($contact) {
|
||||
if ($contact->send_email && $contact->email) {
|
||||
if ($contact->email) {
|
||||
EmailPayment::dispatchNow($this->payment, $this->payment->company, $contact);
|
||||
}
|
||||
});
|
||||
|
@ -77,8 +77,6 @@ class PdfMaker
|
||||
$this->updateVariables($this->data['variables']);
|
||||
}
|
||||
|
||||
$this->processOptions();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -163,144 +163,6 @@ trait PdfMakerUtilities
|
||||
return $element;
|
||||
}
|
||||
|
||||
public function processOptions()
|
||||
{
|
||||
if (!isset($this->options['all_pages_header']) || $this->options['all_pages_header'] == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($this->options['all_pages_footer']) || $this->options['all_pages_footer'] == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->insertPrintCSS();
|
||||
$this->wrapIntoTable();
|
||||
}
|
||||
|
||||
public function insertPrintCSS()
|
||||
{
|
||||
$css = <<<'EOT'
|
||||
table.page-container {
|
||||
page-break-after: always;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
thead.page-header {
|
||||
display: table-header-group;
|
||||
}
|
||||
|
||||
tfoot.page-footer {
|
||||
display: table-footer-group;
|
||||
}
|
||||
EOT;
|
||||
|
||||
$css_node = $this->document->createTextNode($css);
|
||||
|
||||
$style = $this->document->getElementsByTagName('style')->item(0);
|
||||
|
||||
if ($style) {
|
||||
return $style->appendChild($css_node);
|
||||
}
|
||||
|
||||
$head = $this->document->getElementsByTagName('head')->item(0);
|
||||
|
||||
if ($head) {
|
||||
$style_node = $this->document->createElement('style', $css);
|
||||
|
||||
return $head->appendChild($style_node);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function wrapIntoTable()
|
||||
{
|
||||
$markup = <<<'EOT'
|
||||
<table class="page-container" id="page-container">
|
||||
<thead class="page-report">
|
||||
<tr>
|
||||
<th class="page-report-cell" id="repeat-header">
|
||||
<!-- Repeating header goes here.. -->
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot class="report-footer">
|
||||
<tr>
|
||||
<td class="report-footer-cell" id="repeat-footer">
|
||||
<!-- Repeating footer goes here -->
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody class="report-content">
|
||||
<tr>
|
||||
<td class="report-content-cell" id="repeat-content">
|
||||
<!-- Rest of the content goes here -->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
EOT;
|
||||
|
||||
$document = new DOMDocument();
|
||||
$document->loadHTML($markup);
|
||||
|
||||
$table = $document->getElementById('page-container');
|
||||
|
||||
$body = $this->document->getElementsByTagName('body')
|
||||
->item(0);
|
||||
|
||||
$body->appendChild(
|
||||
$this->document->importNode($table, true)
|
||||
);
|
||||
|
||||
for ($i = 0; $i < $body->childNodes->length; $i++) {
|
||||
$element = $body->childNodes->item($i);
|
||||
|
||||
if ($element->nodeType !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
$element->getAttribute('id') == 'header' ||
|
||||
$element->getAttribute('id') == 'footer' ||
|
||||
$element->getAttribute('id') === 'page-container'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$clone = $element->cloneNode(true);
|
||||
$element->parentNode->removeChild($element);
|
||||
|
||||
$this->document->getElementById('repeat-content')->appendChild($clone);
|
||||
}
|
||||
|
||||
// info($this->data['options']);
|
||||
|
||||
if (
|
||||
$header = $this->document->getElementById('header') &&
|
||||
isset($this->data['options']['all_pages_header']) &&
|
||||
$this->data['options']['all_pages_header']
|
||||
) {
|
||||
$header = $this->document->getElementById('header');
|
||||
$clone = $header->cloneNode(true);
|
||||
|
||||
$header->parentNode->removeChild($header);
|
||||
$this->document->getElementById('repeat-header')->appendChild($clone);
|
||||
}
|
||||
|
||||
if (
|
||||
$footer = $this->document->getElementById('footer') &&
|
||||
isset($this->data['options']['all_pages_footer']) &&
|
||||
$this->data['options']['all_pages_footer']
|
||||
) {
|
||||
$footer = $this->document->getElementById('footer');
|
||||
$clone = $footer->cloneNode(true);
|
||||
|
||||
$footer->parentNode->removeChild($footer);
|
||||
$this->document->getElementById('repeat-footer')->appendChild($clone);
|
||||
}
|
||||
}
|
||||
|
||||
public function getEmptyElements(array &$elements, array $variables)
|
||||
{
|
||||
foreach ($elements as &$element) {
|
||||
|
@ -39,7 +39,7 @@ class ConvertQuote
|
||||
{
|
||||
$invoice = CloneQuoteToInvoiceFactory::create($quote, $quote->user_id);
|
||||
$invoice->design_id = $this->decodePrimaryKey($this->client->getSetting('invoice_design_id'));
|
||||
$invoice = $this->invoice_repo->save([], $invoice);
|
||||
$invoice = $this->invoice_repo->save($invoice->toArray(), $invoice);
|
||||
|
||||
$invoice->fresh();
|
||||
|
||||
|
@ -42,7 +42,7 @@ class SendEmail
|
||||
}
|
||||
|
||||
$this->quote->invitations->each(function ($invitation) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->send_email && $invitation->contact->email) {
|
||||
if (!$invitation->contact->trashed() && $invitation->contact->email) {
|
||||
EmailEntity::dispatchNow($invitation, $invitation->company, $this->reminder_template);
|
||||
}
|
||||
});
|
||||
|
@ -165,6 +165,7 @@ class CompanyTransformer extends EntityTransformer
|
||||
'markdown_enabled' => (bool) $company->markdown_enabled,
|
||||
'use_comma_as_decimal_place' => (bool) $company->use_comma_as_decimal_place,
|
||||
'report_include_drafts' => (bool) $company->report_include_drafts,
|
||||
'client_registration_fields' => (array) $company->client_registration_fields,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,7 @@ class HtmlEngine
|
||||
public function __construct($invitation)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
// $invitation->load('contact.client.company', 'company');
|
||||
|
||||
$this->entity_string = $this->resolveEntityString();
|
||||
|
||||
@ -58,7 +59,9 @@ class HtmlEngine
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
$this->client = $this->entity->client;
|
||||
$this->client = $invitation->contact->client;
|
||||
$this->client->load('country','company');
|
||||
$this->entity->load('client');
|
||||
|
||||
$this->settings = $this->client->getMergedSettings();
|
||||
|
||||
@ -113,22 +116,25 @@ class HtmlEngine
|
||||
$data['$total_tax_values'] = ['value' => $this->totalTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$line_tax_labels'] = ['value' => $this->lineTaxLabels(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.date')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.date')];
|
||||
|
||||
$data['$invoice.date'] = &$data['$date'];
|
||||
$data['$invoiceDate'] = &$data['$date'];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
$data['$dueDate'] = &$data['$due_date'];
|
||||
|
||||
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')];
|
||||
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')];
|
||||
$data['$invoice.due_date'] = &$data['$due_date'];
|
||||
$data['$invoice.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: ' ', 'label' => ctrans('texts.po_number')];
|
||||
$data['$poNumber'] = &$data['$invoice.po_number'];
|
||||
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->entity->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.date')];
|
||||
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.date')];
|
||||
$data['$invoice.datetime'] = &$data['$entity.datetime'];
|
||||
$data['$quote.datetime'] = &$data['$entity.datetime'];
|
||||
$data['$credit.datetime'] = &$data['$entity.datetime'];
|
||||
$data['$payment_button'] = ['value' => '<a class="button" href="'.$this->invitation->getPaymentLink().'">'.ctrans('texts.pay_now').'</a>', 'label' => ctrans('texts.pay_now')];
|
||||
$data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')];
|
||||
|
||||
|
||||
if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') {
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.invoice')];
|
||||
@ -140,11 +146,11 @@ class HtmlEngine
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
$data['$viewButton'] = &$data['$view_link'];
|
||||
$data['$view_button'] = &$data['$view_link'];
|
||||
$data['$paymentButton'] = &$data['$view_link'];
|
||||
$data['$paymentButton'] = &$data['$payment_button'];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')];
|
||||
|
||||
if($this->entity->project()->exists()) {
|
||||
if($this->entity->project) {
|
||||
$data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project_name')];
|
||||
}
|
||||
}
|
||||
@ -161,7 +167,7 @@ class HtmlEngine
|
||||
$data['$view_button'] = &$data['$view_link'];
|
||||
$data['$approveButton'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.approve')];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
}
|
||||
|
||||
if ($this->entity_string == 'credit') {
|
||||
@ -176,7 +182,7 @@ class HtmlEngine
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
|
||||
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.credit_date')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.credit_date')];
|
||||
}
|
||||
|
||||
$data['$portal_url'] = ['value' => $this->invitation->getPortalLink(), 'label' =>''];
|
||||
@ -197,7 +203,7 @@ class HtmlEngine
|
||||
if ($this->entity->partial > 0) {
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
|
||||
} else {
|
||||
|
||||
@ -230,7 +236,7 @@ class HtmlEngine
|
||||
$data['$credit.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.credit_number')];
|
||||
$data['$credit.total'] = &$data['$credit.total'];
|
||||
$data['$credit.po_number'] = &$data['$invoice.po_number'];
|
||||
$data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.credit_date')];
|
||||
$data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.credit_date')];
|
||||
$data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')];
|
||||
$data['$credit.balance'] = &$data['$balance'];
|
||||
|
||||
@ -257,13 +263,13 @@ class HtmlEngine
|
||||
$data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")];
|
||||
$data['$your_entity'] = ['value' => '', 'label' => ctrans("texts.your_{$this->entity_string}")];
|
||||
|
||||
$data['$quote.date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
$data['$quote.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
$data['$quote.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.quote_number')];
|
||||
$data['$quote.po_number'] = &$data['$invoice.po_number'];
|
||||
$data['$quote.quote_number'] = &$data['$quote.number'];
|
||||
$data['$quote_no'] = &$data['$quote.number'];
|
||||
$data['$quote.quote_no'] = &$data['$quote.number'];
|
||||
$data['$quote.valid_until'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.valid_until')];
|
||||
$data['$quote.valid_until'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.valid_until')];
|
||||
$data['$valid_until'] = &$data['$quote.valid_until'];
|
||||
$data['$credit_amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_amount')];
|
||||
$data['$credit_balance'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')];
|
||||
@ -457,7 +463,8 @@ class HtmlEngine
|
||||
$data['$auto_bill'] = &$data['$autoBill'];
|
||||
|
||||
/*Payment Aliases*/
|
||||
$data['$paymentLink'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_payment').'</a>', 'label' => ctrans('texts.view_payment')];
|
||||
$data['$paymentLink'] = &$data['$payment_link'];
|
||||
$data['$payment_url'] = &$data['$payment_link'];
|
||||
$data['$portalButton'] = &$data['$paymentLink'];
|
||||
|
||||
$data['$dir'] = ['value' => optional($this->client->language())->locale === 'ar' ? 'rtl' : 'ltr', 'label' => ''];
|
||||
|
@ -82,11 +82,20 @@ class Phantom
|
||||
$url = config('ninja.app_url').'/phantom/'.$entity.'/'.$invitation->key.'?phantomjs_secret='.config('ninja.phantomjs_secret');
|
||||
info($url);
|
||||
|
||||
$key = config('ninja.phantomjs_key');
|
||||
$secret = config('ninja.phantomjs_key');
|
||||
|
||||
$phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$url}%22,renderType:%22pdf%22%7D";
|
||||
$pdf = CurlUtils::get($phantom_url);
|
||||
$key = config( 'ninja.phantomjs_key' );
|
||||
$phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/";
|
||||
$pdf = CurlUtils::post( $phantom_url, json_encode( [
|
||||
'url' => $url,
|
||||
'renderType' => 'pdf',
|
||||
'outputAsJson' => false,
|
||||
'renderSettings' => [
|
||||
'emulateMedia' => 'print',
|
||||
'pdfOptions' => [
|
||||
'preferCSSPageSize' => true,
|
||||
'printBackground' => true,
|
||||
],
|
||||
],
|
||||
] ) );
|
||||
|
||||
$this->checkMime($pdf, $invitation, $entity);
|
||||
|
||||
@ -100,14 +109,20 @@ class Phantom
|
||||
|
||||
public function convertHtmlToPdf($html)
|
||||
{
|
||||
$hash = Str::random(32);
|
||||
Cache::put($hash, $html, 300);
|
||||
|
||||
$url = route('tmp_pdf', ['hash' => $hash]);
|
||||
info($url);
|
||||
$key = config('ninja.phantomjs_key');
|
||||
$phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$url}%22,renderType:%22pdf%22%7D";
|
||||
$pdf = CurlUtils::get($phantom_url);
|
||||
$key = config( 'ninja.phantomjs_key' );
|
||||
$phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/";
|
||||
$pdf = CurlUtils::post( $phantom_url, json_encode( [
|
||||
'content' => $html,
|
||||
'renderType' => 'pdf',
|
||||
'outputAsJson' => false,
|
||||
'renderSettings' => [
|
||||
'emulateMedia' => 'print',
|
||||
'pdfOptions' => [
|
||||
'preferCSSPageSize' => true,
|
||||
'printBackground' => true,
|
||||
],
|
||||
],
|
||||
] ) );
|
||||
|
||||
$response = Response::make($pdf, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
|
@ -43,6 +43,17 @@ trait Inviteable
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function getPaymentLink()
|
||||
{
|
||||
if(Ninja::isHosted()){
|
||||
$domain = $this->company->domain();
|
||||
}
|
||||
else
|
||||
$domain = config('ninja.app_url');
|
||||
|
||||
return $domain.'/client/pay/'.$this->key;
|
||||
}
|
||||
|
||||
public function getLink() :string
|
||||
{
|
||||
$entity_type = Str::snake(class_basename($this->entityType()));
|
||||
|
@ -270,7 +270,7 @@ trait MakesInvoiceValues
|
||||
if (! is_array($items)) {
|
||||
$data;
|
||||
}
|
||||
|
||||
|
||||
$locale_info = localeconv();
|
||||
|
||||
foreach ($items as $key => $item) {
|
||||
@ -296,11 +296,7 @@ trait MakesInvoiceValues
|
||||
|
||||
$data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->client);
|
||||
$data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $this->client);
|
||||
|
||||
/* need to test here as this is new - 18/09/2021*/
|
||||
if(!array_key_exists($table_type.'.gross_line_total', $data[$key]))
|
||||
$data[$key][$table_type.'.gross_line_total'] = 0;
|
||||
|
||||
|
||||
$data[$key][$table_type . ".{$_table_type}1"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}1", $item->custom_value1, $this->client);
|
||||
$data[$key][$table_type . ".{$_table_type}2"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}2", $item->custom_value2, $this->client);
|
||||
$data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->client);
|
||||
@ -314,8 +310,12 @@ trait MakesInvoiceValues
|
||||
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client);
|
||||
|
||||
$data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $this->client);
|
||||
|
||||
|
||||
if(property_exists($item, 'gross_line_total'))
|
||||
$data[$key][$table_type.'.gross_line_total'] = Number::formatMoney($item->gross_line_total, $this->client);
|
||||
else
|
||||
$data[$key][$table_type.'.gross_line_total'] = 0;
|
||||
|
||||
if (isset($item->discount) && $item->discount > 0) {
|
||||
if ($item->is_amount_discount) {
|
||||
$data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $this->client);
|
||||
|
406
composer.lock
generated
406
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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.19',
|
||||
'app_tag' => '5.3.19',
|
||||
'app_version' => '5.3.22',
|
||||
'app_tag' => '5.3.22',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIdealToPaymentTypes extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('payment_types', function (Blueprint $table) {
|
||||
$type = new PaymentType();
|
||||
|
||||
$type->id = 37;
|
||||
$type->name = 'iDEAL';
|
||||
$type->gateway_type_id = GatewayType::IDEAL;
|
||||
|
||||
$type->save();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdatedBoldAndModernDesigns extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
\Illuminate\Support\Facades\Artisan::call('ninja:design-update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
42
public/flutter_service_worker.js
vendored
42
public/flutter_service_worker.js
vendored
@ -3,38 +3,38 @@ const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"/": "0d9690c2b925c794b94e0778817e5c19",
|
||||
"version.json": "9ec5e3813adc4bfd8713556c5059e97d",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1",
|
||||
"assets/NOTICES": "9eb7e2eb2888ea5bae5f536720db37cd",
|
||||
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3",
|
||||
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
|
||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
||||
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
||||
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
|
||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
||||
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
||||
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
||||
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
||||
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
||||
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
|
||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
||||
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
||||
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
||||
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3",
|
||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||
"assets/assets/images/logo_dark.png": "a233ed1d4d0f7414bf97a9a10f11fb0a",
|
||||
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
|
||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1",
|
||||
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
|
||||
"version.json": "9ec5e3813adc4bfd8713556c5059e97d",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"main.dart.js": "9542568225b6ad9e1ffbc87c3e6f74a2"
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"main.dart.js": "7905763d1c2f393f06c8c5b95449e54b",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||
"/": "2b102108c1e1cc925a193f954e3a2b68"
|
||||
};
|
||||
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
|
204444
public/main.dart.js
vendored
204444
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
210264
public/main.foss.dart.js
vendored
210264
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
216217
public/main.last.dart.js
vendored
216217
public/main.last.dart.js
vendored
File diff suppressed because one or more lines are too long
216052
public/main.next.dart.js
vendored
216052
public/main.next.dart.js
vendored
File diff suppressed because one or more lines are too long
11990
public/main.profile.dart.js
vendored
11990
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
201726
public/main.wasm.dart.js
vendored
201726
public/main.wasm.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -4316,8 +4316,8 @@ $LANG = array(
|
||||
'payment_method_cannot_be_preauthorized' => 'This payment method cannot be preauthorized.',
|
||||
'kbc_cbc' => 'KBC/CBC',
|
||||
'bancontact' => 'Bancontact',
|
||||
'sepa_mandat' => 'By providing your IBAN and confirming this payment, you are authorizing :company and Stripe, our payment service provider, to send instructions to your bank to debit your account and your bank to debit your account in accordance with those instructions. You are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited.',
|
||||
'bank_account_holder' => 'Bank Account Holder',
|
||||
'sepa_mandat' => 'By providing your IBAN and confirming this payment, you are authorizing Rocketship Inc. and Stripe, our payment service provider, to send instructions to your bank to debit your account and your bank to debit your account in accordance with those instructions. You are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited.',
|
||||
'ideal' => 'iDEAL',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -13,31 +13,51 @@
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: -0.25cm !important;
|
||||
margin: 0 !important;
|
||||
size: $page_size $page_layout;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* page-break-after: always; */
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
#spacer-table > * > tr > td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#spacer-table{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#header {
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
background-color: #2d2c2a;
|
||||
padding: 3rem;
|
||||
color: white;
|
||||
min-width: 100%;
|
||||
line-height: var(--line-height);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
|
||||
#header, #header-spacer {
|
||||
height: 160px;
|
||||
padding: 3rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.company-logo {
|
||||
height: 100%;
|
||||
padding-right: 120px;
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
object-position: left center;
|
||||
}
|
||||
|
||||
#company-details,
|
||||
@ -46,6 +66,12 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#company-details,
|
||||
#company-address,
|
||||
.logo-container {
|
||||
max-height: 160px;
|
||||
}
|
||||
|
||||
#client-details {
|
||||
margin: 2rem;
|
||||
display: flex;
|
||||
@ -61,7 +87,6 @@
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr;
|
||||
padding-left: 1rem;
|
||||
padding-top: 3rem;
|
||||
}
|
||||
|
||||
.entity-details-wrapper {
|
||||
@ -86,7 +111,11 @@
|
||||
table-layout: fixed;
|
||||
overflow-wrap: break-word;
|
||||
margin-top: 3rem;
|
||||
/* margin-bottom: 200px; */
|
||||
margin-bottom: 200px;
|
||||
}
|
||||
|
||||
[data-ref="table"]:last-child{
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
.task-time-details {
|
||||
@ -138,23 +167,23 @@
|
||||
gap: 80px;
|
||||
}
|
||||
|
||||
#table-totals .totals-table-right-side>* {
|
||||
#table-totals .totals-table-right-side > * {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
#table-totals>.totals-table-right-side>*> :nth-child(1) {
|
||||
#table-totals > .totals-table-right-side > * > :nth-child(1) {
|
||||
text-align: "$dir_text_align";
|
||||
margin-top: .75rem;
|
||||
}
|
||||
|
||||
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) {
|
||||
#table-totals > .totals-table-right-side > * > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
#table-totals>.totals-table-right-side>*> :nth-child(2) {
|
||||
#table-totals > .totals-table-right-side > * > :nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@ -186,51 +215,40 @@
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
#footer {
|
||||
margin-top: 1rem;
|
||||
background-color: #2d2c2a;
|
||||
height: 160px;
|
||||
min-width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding: 1rem 3rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 15px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#footer, #footer-spacer {
|
||||
height: 160px;
|
||||
padding: 1rem 3rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
[data-ref="total_table-footer"] {
|
||||
padding-top: 0.5rem
|
||||
}
|
||||
|
||||
/** Repeating header & footer styling. */
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.print-content {}
|
||||
|
||||
table.print-content th,
|
||||
table.print-content td {
|
||||
table[data-ref="table"] th,
|
||||
table[data-ref="table"] td {
|
||||
padding: .2rem .4rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.print-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.no-print {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
/** Markdown-specific styles. **/
|
||||
#product-table h3,
|
||||
#task-table h3,
|
||||
@ -239,9 +257,9 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
[data-ref="product_table-product.description-th"] {
|
||||
width: 23%;
|
||||
}
|
||||
[data-ref="product_table-product.description-th"] {
|
||||
width: 23%;
|
||||
}
|
||||
|
||||
[data-ref="statement-totals"] {
|
||||
margin-top: 1rem;
|
||||
@ -253,10 +271,6 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
max-height: 160px;
|
||||
}
|
||||
|
||||
#statement-invoice-table-totals > p {
|
||||
margin-right: 2rem;
|
||||
margin-top: 1rem;
|
||||
@ -264,101 +278,94 @@
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
/* .company-logo { display: none } */
|
||||
/** Hide company logo **/
|
||||
/* .company-logo { display: none } */
|
||||
|
||||
/* Hide company details */
|
||||
/* #company-details > * { display: none } */
|
||||
/* Hide company details */
|
||||
/* #company-details > * { display: none } */
|
||||
|
||||
/* Hide company address */
|
||||
/* #company-address > * { display: none } */
|
||||
/* Hide company address */
|
||||
/* #company-address > * { display: none } */
|
||||
|
||||
/* Hide public notes */
|
||||
/* [data-ref="total_table-public_notes"] { display: none } */
|
||||
/* Hide public notes */
|
||||
/* [data-ref="total_table-public_notes"] { display: none } */
|
||||
|
||||
/* Hide terms label */
|
||||
/* [data-ref="total_table-terms-label"] { display: none } */
|
||||
/* Hide terms label */
|
||||
/* [data-ref="total_table-terms-label"] { display: none } */
|
||||
|
||||
/* Hide totals table */
|
||||
/* #table-totals { display: none } */
|
||||
/* Hide totals table */
|
||||
/* #table-totals { display: none } */
|
||||
|
||||
/* Hide totals table left side */
|
||||
/* #table-totals div:first-child > * { display: none !important } */
|
||||
/* Hide totals table left side */
|
||||
/* #table-totals div:first-child > * { display: none !important } */
|
||||
|
||||
/* Hide totals table right side */
|
||||
/* .totals-table-right-side { display: none } */
|
||||
/* Hide totals table right side */
|
||||
/* .totals-table-right-side { display: none } */
|
||||
|
||||
/** For more info, please check our docs: https://invoiceninja.github.io **/
|
||||
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
||||
/** For more info, please check our docs: https://invoiceninja.github.io **/
|
||||
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
||||
</style>
|
||||
|
||||
<table>
|
||||
<!-- Start Header -->
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="header-wrapper" id="header">
|
||||
<div class="logo-container">
|
||||
<img class="company-logo" src="$company.logo" alt="$company.name logo"/>
|
||||
</div>
|
||||
<div id="company-details"></div>
|
||||
<div id="company-address"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- End Header -->
|
||||
<tr>
|
||||
<td id="body">
|
||||
<div class="client-entity-wrapper">
|
||||
<div class="client-wrapper-left-side">
|
||||
<h4 class="entity-label">$entity_label</h4>
|
||||
<div id="client-details" cellspacing="0"></div>
|
||||
</div>
|
||||
<div id="header">
|
||||
<div class="logo-container">
|
||||
<img class="company-logo" src="$company.logo" alt="$company.name logo"/>
|
||||
</div>
|
||||
<div id="company-details"></div>
|
||||
<div id="company-address"></div>
|
||||
</div>
|
||||
<div id="body">
|
||||
<table id="spacer-table" cellspacing="0" >
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<div id="header-spacer"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="client-entity-wrapper">
|
||||
<div class="client-wrapper-left-side">
|
||||
<h4 class="entity-label">$entity_label</h4>
|
||||
<div id="client-details" cellspacing="0"></div>
|
||||
</div>
|
||||
|
||||
<div class="entity-details-wrapper-right-side">
|
||||
<div class="entity-details-wrapper">
|
||||
<table id="entity-details" dir="$dir"></table>
|
||||
<div class="entity-details-wrapper-right-side">
|
||||
<div class="entity-details-wrapper">
|
||||
<table id="entity-details" dir="$dir"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Start Print Content -->
|
||||
<table id="product-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<table id="product-table" cellspacing="0" data-ref="table"></table>
|
||||
|
||||
<table id="task-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<table id="task-table" cellspacing="0" data-ref="table"></table>
|
||||
|
||||
<table id="delivery-note-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
|
||||
|
||||
<table id="statement-invoice-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
|
||||
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
|
||||
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
|
||||
|
||||
<table id="statement-payment-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
|
||||
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
|
||||
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
|
||||
|
||||
<table id="statement-aging-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
|
||||
<!-- End Print Content -->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div id="table-totals" cellspacing="0"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Start Space For Footer -->
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td style="height: 180px">
|
||||
<!-- Leave this empty and don't remove it. This space is where footer placed on print. -->
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<!-- End Space For Footer -->
|
||||
</table>
|
||||
|
||||
<!-- Start Footer -->
|
||||
<div class="footer-wrapper print-footer" id="footer">
|
||||
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
|
||||
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
|
||||
<div id="table-totals" cellspacing="0"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>
|
||||
<div id="footer-spacer"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div>
|
||||
<p data-ref="total_table-footer">$entity_footer</p>
|
||||
|
||||
@ -366,14 +373,13 @@
|
||||
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table-totals', 'statement-payment-table-totals','statement-invoice-table-totals',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table', 'statement-aging-table-totals', 'statement-payment-table-totals'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
document.getElementById(tableIdentifier).childElementCount === 0
|
||||
? document.getElementById(tableIdentifier).style.display = 'none'
|
||||
: '';
|
||||
const el =document.getElementById(tableIdentifier);
|
||||
if(el && el.childElementCount === 0)el.remove()
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -381,4 +387,3 @@
|
||||
<div> <!-- #2 column --> </div>
|
||||
<div> <!-- #3 column --> </div>
|
||||
</div>
|
||||
<!-- End Footer -->
|
||||
|
@ -9,38 +9,54 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: "7px";
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: -0.22cm;
|
||||
size: A4 portrait;
|
||||
margin: 0 !important;
|
||||
size: $page_size $page_layout;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* page-break-after: always; */
|
||||
}
|
||||
|
||||
.header-container {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr;
|
||||
padding: 3rem;
|
||||
min-width: 100%;
|
||||
height: 160px;
|
||||
#spacer-table > * > tr > td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#spacer-table{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#header {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr;
|
||||
position:fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#header, #header-spacer {
|
||||
height: 160px;
|
||||
padding: 3rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.header-container .company-name {
|
||||
font-size: 2rem;
|
||||
#header .company-name {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
#entity-details {
|
||||
@ -55,7 +71,7 @@
|
||||
}
|
||||
|
||||
.logo-client-wrapper {
|
||||
margin: 3rem 2rem;
|
||||
margin: 0 2rem 3rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr;
|
||||
}
|
||||
@ -121,15 +137,18 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
margin-top: 1rem;
|
||||
background-color: var(--primary-color);
|
||||
padding-left: 3rem;
|
||||
padding-right: 3rem;
|
||||
height: 220px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
#footer {
|
||||
background-color: var(--primary-color);
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
#footer, #footer-spacer {
|
||||
height: 220px;
|
||||
padding: 1rem 3rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
@ -204,54 +223,21 @@
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
table.page-container {
|
||||
page-break-after: always;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
thead.page-header {
|
||||
display: table-header-group;
|
||||
}
|
||||
|
||||
tfoot.page-footer {
|
||||
display: table-footer-group;
|
||||
}
|
||||
|
||||
.page-content-cell {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
[data-ref="total_table-footer"] {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/** Repeating header & footer styling. */
|
||||
table {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.print-content {
|
||||
}
|
||||
|
||||
table.print-content th,
|
||||
table.print-content td {
|
||||
padding: 0.2rem 0.4rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.print-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
table[data-ref="table"] th,
|
||||
table[data-ref="table"] td {
|
||||
padding: 0.2rem 0.4rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/** Markdown-specific styles. **/
|
||||
@ -306,70 +292,57 @@
|
||||
/** For more info, please check our docs: https://invoiceninja.github.io **/
|
||||
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
||||
</style>
|
||||
<div id="header">
|
||||
<h1 class="company-name">$company.name</h1>
|
||||
<table id="entity-details" cellspacing="0" dir="$dir"></table>
|
||||
</div>
|
||||
<div id="body">
|
||||
<table id="spacer-table" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<div id="header-spacer"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="logo-client-wrapper">
|
||||
<img class="company-logo" src="$company.logo" alt="$company.name logo"/>
|
||||
<div id="client-details"></div>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table id="product-table" cellspacing="0" data-ref="table"></table>
|
||||
|
||||
<table>
|
||||
<!-- Start Header -->
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="header-container" id="header">
|
||||
<h1 class="company-name">$company.name</h1>
|
||||
<table id="entity-details" cellspacing="0" dir="$dir"></table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- End Header -->
|
||||
<tr>
|
||||
<td id="body">
|
||||
<div class="logo-client-wrapper">
|
||||
<img
|
||||
class="company-logo"
|
||||
src="$company.logo"
|
||||
alt="$company.name logo"
|
||||
/>
|
||||
<table id="task-table" cellspacing="0" data-ref="table"></table>
|
||||
|
||||
<div id="client-details"></div>
|
||||
</div>
|
||||
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
|
||||
|
||||
<!-- Start Print Content -->
|
||||
<div class="table-wrapper">
|
||||
<table id="product-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
|
||||
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
|
||||
|
||||
<table id="task-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
|
||||
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
|
||||
|
||||
<table id="delivery-note-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
|
||||
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
|
||||
</div>
|
||||
<div id="table-totals" cellspacing="0"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>
|
||||
<div id="footer-spacer"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table id="statement-invoice-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
|
||||
|
||||
<table id="statement-payment-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
|
||||
|
||||
<table id="statement-aging-table" cellspacing="0" class="print-content" data-ref="table"></table>
|
||||
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
|
||||
</div>
|
||||
<!-- End Print Content -->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div id="table-totals" cellspacing="0"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Start Space For Footer -->
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td style="height: 230px">
|
||||
<!-- Leave this empty and don't remove it. This space is where footer placed on print -->
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<!-- End Space For Footer -->
|
||||
</table>
|
||||
|
||||
<!-- Start Footer -->
|
||||
<div class="footer-wrapper print-footer" id="footer">
|
||||
<div id="footer">
|
||||
<div class="footer-content">
|
||||
<div>
|
||||
<p data-ref="total_table-footer">$entity_footer</p>
|
||||
@ -378,14 +351,14 @@
|
||||
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table-totals', 'statement-payment-table-totals','statement-invoice-table-totals',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table', 'statement-aging-table-totals', 'statement-payment-table-totals'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
document.getElementById(tableIdentifier).childElementCount === 0
|
||||
? document.getElementById(tableIdentifier).style.display = 'none'
|
||||
: '';
|
||||
? document.getElementById(tableIdentifier).remove()
|
||||
: '';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -396,4 +369,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Footer -->
|
||||
|
@ -13,9 +13,13 @@
|
||||
<div class="{{ $account && !$account->isPaid() ? 'col-span-2' : 'col-span-3' }} h-screen flex">
|
||||
<div class="m-auto w-1/2 md:w-1/3 lg:w-1/4">
|
||||
@if($account && !$account->isPaid())
|
||||
<div>
|
||||
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}" class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">
|
||||
</div>
|
||||
<div>
|
||||
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}" class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">
|
||||
</div>
|
||||
@elseif(isset($company) && !is_null($company))
|
||||
<div>
|
||||
<img src="{{ asset($company->present()->logo()) }}" class="h-14 mb-10" alt="{{ $company->present()->name() }} logo">
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-center text-3xl">{{ ctrans('texts.password_recovery') }}</h1>
|
||||
|
@ -13,11 +13,14 @@
|
||||
|
||||
<div class="{{ $account && !$account->isPaid() ? 'col-span-2' : 'col-span-3' }} h-screen flex">
|
||||
<div class="m-auto w-1/2 md:w-1/3 lg:w-1/4">
|
||||
|
||||
@if($account && !$account->isPaid())
|
||||
<div>
|
||||
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}" class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">
|
||||
</div>
|
||||
<div>
|
||||
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}" class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">
|
||||
</div>
|
||||
@elseif(isset($company) && !is_null($company))
|
||||
<div>
|
||||
<img src="{{ asset($company->present()->logo()) }}" class="h-14 mb-10" alt="{{ $company->present()->name() }} logo">
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-center text-3xl">{{ ctrans('texts.password_recovery') }}</h1>
|
||||
|
@ -14,6 +14,7 @@
|
||||
@csrf
|
||||
|
||||
<div class="grid grid-cols-12 gap-4 mt-10">
|
||||
@if($company->client_registration_fields)
|
||||
@foreach($company->client_registration_fields as $field)
|
||||
@if($field['required'])
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
@ -96,6 +97,7 @@
|
||||
@endif
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-8">
|
||||
|
@ -0,0 +1,8 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'iDEAL', 'card_title' =>
|
||||
'iDEAL'])
|
||||
|
||||
@section('gateway_content')
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
{{ __('texts.payment_method_cannot_be_preauthorized') }}
|
||||
@endcomponent
|
||||
@endsection
|
@ -67,7 +67,7 @@
|
||||
{{ ctrans('texts.invoice_date') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $invoice->date }}
|
||||
{{ $invoice->formatDate($invoice->date, $invoice->client->date_format()) }}
|
||||
</dd>
|
||||
@endif
|
||||
</div>
|
||||
|
@ -39,7 +39,7 @@
|
||||
<input type="checkbox" class="form-checkbox mr-1"
|
||||
name="send_logs" {{ old('send_logs' ? 'checked': '') }}>
|
||||
<span>{{ ctrans('texts.send_fail_logs_to_our_server') }}</span>
|
||||
<a class="button-link mt-1 block" href="https://www.invoiceninja.com/privacy-policy/">Read more
|
||||
<a class="button-link mt-1 block" target="_blank" href="https://www.invoiceninja.com/privacy-policy/">Read more
|
||||
about how we use this.</a>
|
||||
</dd>
|
||||
</div>
|
||||
|
@ -10,7 +10,12 @@
|
||||
<div class="col-span-12 md:col-start-4 md:col-span-6 mt-4 md:mt-10">
|
||||
<h1 class="text-center text-2xl font-semibold">Invoice Ninja Setup</h1>
|
||||
<p class="text-sm text-center">{{ ctrans('texts.if_you_need_help') }}
|
||||
<a href="https://www.invoiceninja.com/forums/forum/support/" class="button-link">{{ ctrans('texts.support_forum') }}</a>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://www.invoiceninja.com/forums/forum/support/"
|
||||
class="button-link underline">
|
||||
{{ ctrans('texts.support_forum') }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@if($errors->any())
|
||||
|
@ -103,6 +103,7 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie
|
||||
Route::get('quote/{invitation_key}/download_pdf', 'QuoteController@downloadPdf')->name('quote.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('pay/{invitation_key}', 'ClientPortal\InvitationController@payInvoice')->name('pay.invoice');
|
||||
// Route::get('{entity}/{client_hash}/{invitation_key}', 'ClientPortal\InvitationController@routerForIframe')->name('invoice.client_hash_and_invitation_key'); //should never need this
|
||||
|
||||
});
|
||||
|
106
tests/Browser/ClientPortal/Gateways/Mollie/IDEALTest.php
Normal file
106
tests/Browser/ClientPortal/Gateways/Mollie/IDEALTest.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?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 Tests\Browser\ClientPortal\Gateways\Mollie;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
use Tests\Browser\Pages\ClientPortal\Login;
|
||||
|
||||
class IDEALTest extends DuskTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
foreach (static::$browsers as $browser) {
|
||||
$browser->driver->manage()->deleteAllCookies();
|
||||
}
|
||||
|
||||
$this->disableCompanyGateways();
|
||||
|
||||
CompanyGateway::where('gateway_key', '1bd651fb213ca0c9d66ae3c336dc77e8')->restore();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visit(new Login())
|
||||
->auth();
|
||||
});
|
||||
}
|
||||
|
||||
public function testSuccessfulPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('iDEAL')
|
||||
->waitForText('Test profile')
|
||||
->press('ABN AMRO')
|
||||
->radio('final_state', 'paid')
|
||||
->press('Continue')
|
||||
->waitForText('Details of the payment')
|
||||
->assertSee('Completed');
|
||||
});
|
||||
}
|
||||
|
||||
public function testOpenPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('iDEAL')
|
||||
->waitForText('Test profile')
|
||||
->press('ABN AMRO')
|
||||
->radio('final_state', 'open')
|
||||
->press('Continue')
|
||||
->waitForText('Details of the payment')
|
||||
->assertSee('Pending');
|
||||
});
|
||||
}
|
||||
|
||||
public function testFailedPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('iDEAL')
|
||||
->waitForText('Test profile')
|
||||
->press('ABN AMRO')
|
||||
->radio('final_state', 'failed')
|
||||
->press('Continue')
|
||||
->waitForText('Failed.');
|
||||
});
|
||||
}
|
||||
|
||||
public function testCancelledPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('iDEAL')
|
||||
->waitForText('Test profile')
|
||||
->press('ABN AMRO')
|
||||
->radio('final_state', 'canceled')
|
||||
->press('Continue')
|
||||
->waitForText('Cancelled.');
|
||||
});
|
||||
}
|
||||
}
|
@ -93,9 +93,9 @@ class CreditsTest extends TestCase
|
||||
$this->actingAs($client->contacts->first(), 'contact');
|
||||
|
||||
Livewire::test(CreditsTable::class, ['company' => $company])
|
||||
->assertSee('testing-number-01')
|
||||
->assertDontSee('testing-number-01')
|
||||
->assertSee('testing-number-02')
|
||||
->assertDontSee('testing-number-03');
|
||||
->assertSee('testing-number-03');
|
||||
}
|
||||
|
||||
public function testShowingCreditsWithNullDueDate()
|
||||
@ -122,6 +122,7 @@ class CreditsTest extends TestCase
|
||||
'client_id' => $client->id,
|
||||
'number' => 'testing-number-01',
|
||||
'status_id' => Credit::STATUS_SENT,
|
||||
'due_date' => null,
|
||||
]);
|
||||
|
||||
Credit::factory()->create([
|
||||
@ -142,12 +143,21 @@ class CreditsTest extends TestCase
|
||||
'status_id' => Credit::STATUS_SENT,
|
||||
]);
|
||||
|
||||
Credit::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
'client_id' => $client->id,
|
||||
'number' => 'testing-number-04',
|
||||
'due_date' => '',
|
||||
'status_id' => Credit::STATUS_SENT,
|
||||
]);
|
||||
|
||||
$this->actingAs($client->contacts->first(), 'contact');
|
||||
|
||||
Livewire::test(CreditsTable::class, ['company' => $company])
|
||||
->assertSee('testing-number-01')
|
||||
->assertSee('testing-number-02')
|
||||
->assertDontSee('testing-number-03');
|
||||
->assertSee('testing-number-03');
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user