Merge pull request #7159 from turbo124/v5-develop

v5.3.51
This commit is contained in:
David Bomba 2022-01-28 07:34:44 +11:00 committed by GitHub
commit 2fe5d6feba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 506 additions and 84 deletions

View File

@ -1 +1 @@
5.3.49 5.3.51

View File

@ -76,8 +76,25 @@ class GmailTransport extends Transport
} }
/**
* Google is very strict with their
* sending limits, if we hit 429s, sleep and
* retry again later.
*/
try{
$this->gmail->send(); $this->gmail->send();
}
catch(\Google\Service\Exception $e)
{
nlog("gmail exception");
nlog($e->getErrors());
sleep(5);
$this->gmail->send();
}
$this->sendPerformed($message); $this->sendPerformed($message);
return $this->numberOfRecipients($message); return $this->numberOfRecipients($message);

View File

@ -152,6 +152,7 @@ class ContactLoginController extends Controller
public function logout() public function logout()
{ {
Auth::guard('contact')->logout(); Auth::guard('contact')->logout();
request()->session()->invalidate();
return redirect('/client/login'); return redirect('/client/login');
} }

View File

@ -98,6 +98,7 @@ class InvitationController extends Controller
$client_contact->email = Str::random(15) . "@example.com"; $client_contact->save(); $client_contact->email = Str::random(15) . "@example.com"; $client_contact->save();
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) { if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
request()->session()->invalidate();
auth()->guard('contact')->loginUsingId($client_contact->id, true); auth()->guard('contact')->loginUsingId($client_contact->id, true);
} elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) { } elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
@ -106,6 +107,7 @@ class InvitationController extends Controller
} else { } else {
nlog("else - default - login contact"); nlog("else - default - login contact");
request()->session()->invalidate();
auth()->guard('contact')->loginUsingId($client_contact->id, true); auth()->guard('contact')->loginUsingId($client_contact->id, true);
} }

View File

@ -90,12 +90,15 @@ class PaymentController extends Controller
public function response(PaymentResponseRequest $request) public function response(PaymentResponseRequest $request)
{ {
$gateway = CompanyGateway::findOrFail($request->input('company_gateway_id'));
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->payment_hash])->first(); $gateway = CompanyGateway::findOrFail($request->input('company_gateway_id'));
$payment_hash = PaymentHash::where('hash', $request->payment_hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice ? $invoice->client : auth()->user()->client;
return $gateway return $gateway
->driver(auth()->user()->client) // ->driver(auth()->user()->client)
->driver($client)
->setPaymentMethod($request->input('payment_method_id')) ->setPaymentMethod($request->input('payment_method_id'))
->setPaymentHash($payment_hash) ->setPaymentHash($payment_hash)
->checkRequirements() ->checkRequirements()

View File

@ -401,7 +401,7 @@ class DesignController extends BaseController
} }
$design->design = $d; $design->design = $d;
$design->save(); // $design->save();
/* /*
This is required as the base template does not know to inject the table elements This is required as the base template does not know to inject the table elements

View File

@ -18,6 +18,7 @@ use App\Factory\CloneQuoteToInvoiceFactory;
use App\Factory\QuoteFactory; use App\Factory\QuoteFactory;
use App\Filters\QuoteFilters; use App\Filters\QuoteFilters;
use App\Http\Requests\Quote\ActionQuoteRequest; use App\Http\Requests\Quote\ActionQuoteRequest;
use App\Http\Requests\Quote\BulkActionQuoteRequest;
use App\Http\Requests\Quote\CreateQuoteRequest; use App\Http\Requests\Quote\CreateQuoteRequest;
use App\Http\Requests\Quote\DestroyQuoteRequest; use App\Http\Requests\Quote\DestroyQuoteRequest;
use App\Http\Requests\Quote\EditQuoteRequest; use App\Http\Requests\Quote\EditQuoteRequest;
@ -510,7 +511,7 @@ class QuoteController extends BaseController
* ), * ),
* ) * )
*/ */
public function bulk() public function bulk(BulkActionQuoteRequest $request)
{ {
$action = request()->input('action'); $action = request()->input('action');

View File

@ -454,6 +454,7 @@ class BillingPortalPurchase extends Component
$contact = ClientContact::query() $contact = ClientContact::query()
->where('email', $this->email) ->where('email', $this->email)
->where('company_id', $this->subscription->company_id)
->first(); ->first();
$mailer = new NinjaMailerObject(); $mailer = new NinjaMailerObject();

View File

@ -37,6 +37,7 @@ class ContactKeyLogin
{ {
if (Auth::guard('contact')->check()) { if (Auth::guard('contact')->check()) {
Auth::guard('contact')->logout(); Auth::guard('contact')->logout();
$request->session()->invalidate();
} }
if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) { if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {

View File

@ -35,7 +35,7 @@ class SessionDomains
if (strpos($domain_name, 'invoicing.co') !== false) if (strpos($domain_name, 'invoicing.co') !== false)
{ {
config(['session.domain' => '.invoicing.co']); // config(['session.domain' => '.invoicing.co']);
} }
else{ else{

View File

@ -34,10 +34,10 @@ class UpdateExpenseRequest extends Request
public function rules() public function rules()
{ {
/* Ensure we have a client name, and that all emails are unique*/ /* Ensure we have a client name, and that all emails are unique*/
$rules = [];
// $rules['country_id'] = 'integer|nullable';
$rules['country_id'] = 'integer|nullable'; // $rules['contacts.*.email'] = 'nullable|distinct';
$rules['contacts.*.email'] = 'nullable|distinct';
if (isset($this->number)) { if (isset($this->number)) {
$rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id); $rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id);

View File

@ -0,0 +1,41 @@
<?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\Http\Requests\Quote;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Quote\ConvertableQuoteRule;
class BulkActionQuoteRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return true;
}
public function rules()
{
$input = $this->all();
$rules = [];
if($input['action'] == 'convert_to_invoice')
$rules['action'] = [new ConvertableQuoteRule()];
return $rules;
}
}

View File

@ -74,6 +74,7 @@ class PaymentAppliedValidAmount implements Rule
} }
} }
return $payment_amounts >= $invoice_amounts; // nlog("{round($payment_amounts,3)} >= {round($invoice_amounts,3)}");
return round($payment_amounts,3) >= round($invoice_amounts,3);
} }
} }

View File

@ -0,0 +1,67 @@
<?php
/**
* Quote Ninja (https://quoteninja.com).
*
* @link https://github.com/quoteninja/quoteninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://quoteninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Quote;
use App\Models\Quote;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Validation\Rule;
/**
* Class ConvertableQuoteRule.
*/
class ConvertableQuoteRule implements Rule
{
use MakesHash;
public function __construct()
{
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return $this->checkQuoteIsConvertable(); //if it exists, return false!
}
/**
* @return string
*/
public function message()
{
return ctrans('texts.quote_has_expired');
}
/**
* @return bool
*/
private function checkQuoteIsConvertable() : bool
{
$ids = request()->input('ids');
$quotes = Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
foreach($quotes as $quote){
if(!$quote->service()->isConvertable())
return false;
}
return true;
}
}

View File

@ -45,7 +45,7 @@ class InvoiceTransformer extends BaseTransformer {
'client_id' => $this->getClient( $this->getString( $invoice_data, 'client.name' ), $this->getString( $invoice_data, 'client.email' ) ), 'client_id' => $this->getClient( $this->getString( $invoice_data, 'client.name' ), $this->getString( $invoice_data, 'client.email' ) ),
'discount' => $this->getFloat( $invoice_data, 'invoice.discount' ), 'discount' => $this->getFloat( $invoice_data, 'invoice.discount' ),
'po_number' => $this->getString( $invoice_data, 'invoice.po_number' ), 'po_number' => $this->getString( $invoice_data, 'invoice.po_number' ),
'date' => isset( $invoice_data['invoice.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.date'] ) ) : null, 'date' => isset( $invoice_data['invoice.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.date'] ) ) : now()->format('Y-m-d'),
'due_date' => isset( $invoice_data['invoice.due_date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.due_date'] ) ) : null, 'due_date' => isset( $invoice_data['invoice.due_date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.due_date'] ) ) : null,
'terms' => $this->getString( $invoice_data, 'invoice.terms' ), 'terms' => $this->getString( $invoice_data, 'invoice.terms' ),
'public_notes' => $this->getString( $invoice_data, 'invoice.public_notes' ), 'public_notes' => $this->getString( $invoice_data, 'invoice.public_notes' ),
@ -72,7 +72,7 @@ class InvoiceTransformer extends BaseTransformer {
'status_id' => $invoiceStatusMap[ $status = 'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'invoice.status' ) ) ] ?? strtolower( $this->getString( $invoice_data, 'invoice.status' ) ) ] ??
Invoice::STATUS_SENT, Invoice::STATUS_SENT,
'viewed' => $status === 'viewed', // 'viewed' => $status === 'viewed',
'archived' => $status === 'archived', 'archived' => $status === 'archived',
]; ];

View File

@ -35,7 +35,8 @@ class InvoiceTransformer extends BaseTransformer {
'company_id' => $this->maps['company']->id, 'company_id' => $this->maps['company']->id,
'client_id' => $this->getClient( $customer_name = $this->getString( $invoice_data, 'Customer' ), null ), 'client_id' => $this->getClient( $customer_name = $this->getString( $invoice_data, 'Customer' ), null ),
'number' => $invoice_number = $this->getString( $invoice_data, 'Invoice Number' ), 'number' => $invoice_number = $this->getString( $invoice_data, 'Invoice Number' ),
'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ) : null, 'date' => date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ) ?: now()->format('Y-m-d'), //27-01-2022
// 'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ) : null,
'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ), 'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'status_id' => Invoice::STATUS_SENT, 'status_id' => Invoice::STATUS_SENT,
]; ];

View File

@ -1381,7 +1381,13 @@ class CompanyImport implements ShouldQueue
$new_obj->company_id = $this->company->id; $new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array); $new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]); $new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextRecurringExpenseNumber($client = Client::find($obj_array['client_id']), $new_obj); $new_obj->number = $this->getNextRecurringExpenseNumber($new_obj);
}
elseif($class == 'App\Models\CompanyLedger'){
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
$obj_array,
);
} }
else{ else{
$new_obj = $class::withTrashed()->firstOrNew( $new_obj = $class::withTrashed()->firstOrNew(

View File

@ -211,15 +211,15 @@ class NinjaMailerJob implements ShouldQueue
} }
//17-01-2022 - ensure we have a token otherwise we fail gracefully to default sending engine //17-01-2022 - ensure we have a token otherwise we fail gracefully to default sending engine
if(strlen($user->oauth_user_token) == 0){ // if(strlen($user->oauth_user_token) == 0){
$this->nmo->settings->email_sending_method = 'default'; // $this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver(); // return $this->setMailDriver();
} // }
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token)); $google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
//need to slow down gmail requests otherwise we hit 429's //need to slow down gmail requests otherwise we hit 429's
sleep(rand(1,3)); sleep(rand(2,6));
} }
catch(\Exception $e) { catch(\Exception $e) {
$this->logMailError('Gmail Token Invalid', $this->company->clients()->first()); $this->logMailError('Gmail Token Invalid', $this->company->clients()->first());

View File

@ -97,7 +97,7 @@ class Gateway extends StaticModel
]; ];
case 15: case 15:
return [ return [
GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false] GatewayType::PAYPAL => ['refund' => false, 'token_billing' => false]
]; //Paypal ]; //Paypal
break; break;
case 20: case 20:
@ -140,15 +140,15 @@ class Gateway extends StaticModel
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']], GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']],
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false], GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false], GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], //Stripe GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']], //Stripe
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']],
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']],
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']],
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']],
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']],
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']],
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']],
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']],
]; ];
break; break;
case 57: case 57:

View File

@ -13,6 +13,7 @@ namespace App\Models;
use App\Events\Payment\PaymentWasRefunded; use App\Events\Payment\PaymentWasRefunded;
use App\Events\Payment\PaymentWasVoided; use App\Events\Payment\PaymentWasVoided;
use App\Models\GatewayType;
use App\Services\Ledger\LedgerService; use App\Services\Ledger\LedgerService;
use App\Services\Payment\PaymentService; use App\Services\Payment\PaymentService;
use App\Utils\Ninja; use App\Utils\Ninja;
@ -148,6 +149,11 @@ class Payment extends BaseModel
return $this->belongsTo(PaymentType::class); return $this->belongsTo(PaymentType::class);
} }
public function gateway_type()
{
return $this->belongsTo(GatewayType::class);
}
public function paymentables() public function paymentables()
{ {
return $this->hasMany(Paymentable::class); return $this->hasMany(Paymentable::class);

View File

@ -156,7 +156,8 @@ class AuthorizePaymentMethod
$paymentOne = new PaymentType(); $paymentOne = new PaymentType();
$paymentOne->setOpaqueData($op); $paymentOne->setOpaqueData($op);
$contact = $this->authorize->client->primary_contact()->first(); $contact = $this->authorize->client->primary_contact()->first() ?: $this->authorize->client->contacts()->first();
$billto = false; $billto = false;
if ($contact) { if ($contact) {

View File

@ -418,13 +418,16 @@ class BaseDriver extends AbstractPaymentDriver
throw new PaymentFailed($error, $e->getCode()); throw new PaymentFailed($error, $e->getCode());
} }
public function sendFailureMail($error = '') public function sendFailureMail($error)
{ {
if (!is_null($this->payment_hash)) { if (!is_null($this->payment_hash)) {
$this->unWindGatewayFees($this->payment_hash); $this->unWindGatewayFees($this->payment_hash);
} }
if(!$error)
$error = '';
PaymentFailedMailer::dispatch( PaymentFailedMailer::dispatch(
$this->payment_hash, $this->payment_hash,
$this->client->company, $this->client->company,

View File

@ -143,6 +143,10 @@ class ACSS
'payment_method_types' => ['acss_debit'], 'payment_method_types' => ['acss_debit'],
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::ACSS,
],
'payment_method_options' => [ 'payment_method_options' => [
'acss_debit' => [ 'acss_debit' => [
'mandate_options' => [ 'mandate_options' => [
@ -150,7 +154,6 @@ class ACSS
'interval_description' => 'when any invoice becomes due', 'interval_description' => 'when any invoice becomes due',
'transaction_type' => 'personal' // TODO: check if is company or personal https://stripe.com/docs/payments/acss-debit 'transaction_type' => 'personal' // TODO: check if is company or personal https://stripe.com/docs/payments/acss-debit
], ],
'currency' => $this->stripe->client->currency()->code,
] ]
] ]
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);

View File

@ -48,6 +48,10 @@ class ApplePay
$data['intent'] = \Stripe\PaymentIntent::create([ $data['intent'] = \Stripe\PaymentIntent::create([
'amount' => $data['stripe_amount'], 'amount' => $data['stripe_amount'],
'currency' => $this->stripe_driver->client->getCurrencyCode(), 'currency' => $this->stripe_driver->client->getCurrencyCode(),
'metadata' => [
'payment_hash' => $this->stripe_driver->payment_hash->hash,
'gateway_type_id' => GatewayType::APPLE_PAY,
],
], $this->stripe_driver->stripe_connect_auth); ], $this->stripe_driver->stripe_connect_auth);
$this->stripe_driver->payment_hash->data = array_merge((array) $this->stripe_driver->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]); $this->stripe_driver->payment_hash->data = array_merge((array) $this->stripe_driver->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]);

View File

@ -56,7 +56,10 @@ class BECS
'setup_future_usage' => 'off_session', 'setup_future_usage' => 'off_session',
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::BECS,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret; $data['pi_client_secret'] = $intent->client_secret;

View File

@ -52,6 +52,10 @@ class Bancontact
'payment_method_types' => ['bancontact'], 'payment_method_types' => ['bancontact'],
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::BANCONTACT,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);

View File

@ -71,6 +71,10 @@ class BrowserPay implements MethodInterface
'currency' => $this->stripe->client->getCurrencyCode(), 'currency' => $this->stripe->client->getCurrencyCode(),
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::APPLE_PAY,
],
]; ];
$data['gateway'] = $this->stripe; $data['gateway'] = $this->stripe;

View File

@ -78,6 +78,10 @@ class Charge
'customer' => $cgt->gateway_customer_reference, 'customer' => $cgt->gateway_customer_reference,
'confirm' => true, 'confirm' => true,
'description' => $description, 'description' => $description,
'metadata' => [
'payment_hash' => $payment_hash->hash,
'gateway_type_id' => GatewayType::CREDIT_CARD,
],
]; ];
$response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth); $response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth);

View File

@ -63,10 +63,13 @@ class CreditCard
'currency' => $this->stripe->client->getCurrencyCode(), 'currency' => $this->stripe->client->getCurrencyCode(),
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::CREDIT_CARD,
],
'setup_future_usage' => 'off_session',
]; ];
$payment_intent_data['setup_future_usage'] = 'off_session';
$data['intent'] = $this->stripe->createPaymentIntent($payment_intent_data); $data['intent'] = $this->stripe->createPaymentIntent($payment_intent_data);
$data['gateway'] = $this->stripe; $data['gateway'] = $this->stripe;

View File

@ -52,7 +52,10 @@ class EPS
'payment_method_types' => ['eps'], 'payment_method_types' => ['eps'],
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::EPS,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret; $data['pi_client_secret'] = $intent->client_secret;

View File

@ -53,7 +53,10 @@ class FPX
'payment_method_types' => ['fpx'], 'payment_method_types' => ['fpx'],
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::FPX,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret; $data['pi_client_secret'] = $intent->client_secret;

View File

@ -52,7 +52,10 @@ class GIROPAY
'payment_method_types' => ['giropay'], 'payment_method_types' => ['giropay'],
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::GIROPAY,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret; $data['pi_client_secret'] = $intent->client_secret;

View File

@ -0,0 +1,201 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\Stripe\Jobs;
use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\Utilities;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class PaymentIntentWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Utilities;
public $tries = 1; //number of retries
public $deleteWhenMissingModels = true;
public $stripe_request;
public $company_key;
private $company_gateway_id;
public $payment_completed = false;
public function __construct($stripe_request, $company_key, $company_gateway_id)
{
$this->stripe_request = $stripe_request;
$this->company_key = $company_key;
$this->company_gateway_id = $company_gateway_id;
}
public function handle()
{
// nlog($this->stripe_request);
// nlog(optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id']);
// nlog(optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['payment_hash']);
// nlog(optional($this->stripe_request['object']['charges']['data'][0]['payment_method_details']['card'])['brand']);
MultiDB::findAndSetDbByCompanyKey($this->company_key);
$company = Company::where('company_key', $this->company_key)->first();
foreach ($this->stripe_request as $transaction) {
if(array_key_exists('payment_intent', $transaction))
{
$payment = Payment::query()
->where('company_id', $company->id)
->where(function ($query) use ($transaction) {
$query->where('transaction_reference', $transaction['payment_intent'])
->orWhere('transaction_reference', $transaction['id']);
})
->first();
}
else
{
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $transaction['id'])
->first();
}
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
$this->payment_completed = true;
}
}
if($this->payment_completed)
return;
if(optional($this->stripe_request['object']['charges']['data'][0])['id']){
$company = Company::where('company_key', $this->company_key)->first();
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $this->stripe_request['object']['charges']['data'][0]['id'])
->first();
//return early
if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
nlog(" payment found and status correct - returning ");
return;
}
elseif($payment){
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
$hash = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
nlog("no payment found");
if(optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['allowed_source_types']))
{
nlog("hash found");
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client);
}
}
}
private function updateCreditCardPayment($payment_hash, $client)
{
$company_gateway = CompanyGateway::find($this->company_gateway_id);
$payment_method_type = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id'];
$driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type);
$payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request);
$payment_hash->save();
$driver->setPaymentHash($payment_hash);
$data = [
'payment_method' => $payment_hash->data->object->payment_method,
'payment_type' => PaymentType::parseCardType(strtolower(optional($this->stripe_request['object']['charges']['data'][0]['payment_method_details']['card'])['brand'])) ?: PaymentType::CREDIT_CARD_OTHER,
'amount' => $payment_hash->data->amount_with_fee,
'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'],
'gateway_type_id' => GatewayType::CREDIT_CARD,
];
$payment = $driver->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $this->stripe_request, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_STRIPE,
$client,
$client->company,
);
}
//charge # optional($this->stripe_request['object']['charges']['data'][0])['id']
//metadata # optional($this->stripe_request['object']['charges']['data'][0]['metadata']['gateway_type_id']
//metadata # optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']
/**
*
* $intent = \Stripe\PaymentIntent::retrieve('{{PAYMENT_INTENT_ID}}');
$charges = $intent->charges->data;
*
*
* $payment = Payment::query()
->where('company_id', $request->getCompany()->id)
->where('transaction_reference', $transaction['id'])
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
* */
}

View File

@ -52,7 +52,10 @@ class PRZELEWY24
'payment_method_types' => ['p24'], 'payment_method_types' => ['p24'],
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::PRZELEWY24,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret; $data['pi_client_secret'] = $intent->client_secret;

View File

@ -54,6 +54,10 @@ class SEPA
'setup_future_usage' => 'off_session', 'setup_future_usage' => 'off_session',
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::SEPA,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret; $data['pi_client_secret'] = $intent->client_secret;

View File

@ -52,7 +52,10 @@ class SOFORT
'payment_method_types' => ['sofort'], 'payment_method_types' => ['sofort'],
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::SOFORT,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret; $data['pi_client_secret'] = $intent->client_secret;

View File

@ -18,7 +18,7 @@ trait Utilities
public function convertFromStripeAmount($amount, $precision, $currency) public function convertFromStripeAmount($amount, $precision, $currency)
{ {
if(in_array($amount, ["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"])) if(in_array($currency->code, ["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"]))
return $amount; return $amount;
return $amount / pow(10, $precision); return $amount / pow(10, $precision);
@ -28,7 +28,7 @@ trait Utilities
public function convertToStripeAmount($amount, $precision, $currency) public function convertToStripeAmount($amount, $precision, $currency)
{ {
if(in_array($amount, ["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"])) if(in_array($currency->code, ["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"]))
return $amount; return $amount;
return round(($amount * pow(10, $precision)),0); return round(($amount * pow(10, $precision)),0);

View File

@ -52,7 +52,10 @@ class iDeal
'payment_method_types' => ['ideal'], 'payment_method_types' => ['ideal'],
'customer' => $this->stripe->findOrCreateCustomer(), 'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')), 'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::IDEAL,
],
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret; $data['pi_client_secret'] = $intent->client_secret;

View File

@ -25,25 +25,26 @@ use App\Models\PaymentHash;
use App\Models\PaymentType; use App\Models\PaymentType;
use App\Models\SystemLog; use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\ACH; use App\PaymentDrivers\Stripe\ACH;
use App\PaymentDrivers\Stripe\ACSS;
use App\PaymentDrivers\Stripe\Alipay; use App\PaymentDrivers\Stripe\Alipay;
use App\PaymentDrivers\Stripe\ApplePay; use App\PaymentDrivers\Stripe\ApplePay;
use App\PaymentDrivers\Stripe\BECS;
use App\PaymentDrivers\Stripe\Bancontact;
use App\PaymentDrivers\Stripe\BrowserPay;
use App\PaymentDrivers\Stripe\Charge; use App\PaymentDrivers\Stripe\Charge;
use App\PaymentDrivers\Stripe\Connect\Verify; use App\PaymentDrivers\Stripe\Connect\Verify;
use App\PaymentDrivers\Stripe\CreditCard; use App\PaymentDrivers\Stripe\CreditCard;
use App\PaymentDrivers\Stripe\ImportCustomers;
use App\PaymentDrivers\Stripe\SOFORT;
use App\PaymentDrivers\Stripe\SEPA;
use App\PaymentDrivers\Stripe\PRZELEWY24;
use App\PaymentDrivers\Stripe\GIROPAY;
use App\PaymentDrivers\Stripe\iDeal;
use App\PaymentDrivers\Stripe\EPS; use App\PaymentDrivers\Stripe\EPS;
use App\PaymentDrivers\Stripe\Bancontact;
use App\PaymentDrivers\Stripe\BECS;
use App\PaymentDrivers\Stripe\ACSS;
use App\PaymentDrivers\Stripe\BrowserPay;
use App\PaymentDrivers\Stripe\FPX; use App\PaymentDrivers\Stripe\FPX;
use App\PaymentDrivers\Stripe\GIROPAY;
use App\PaymentDrivers\Stripe\ImportCustomers;
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentWebhook;
use App\PaymentDrivers\Stripe\PRZELEWY24;
use App\PaymentDrivers\Stripe\SEPA;
use App\PaymentDrivers\Stripe\SOFORT;
use App\PaymentDrivers\Stripe\UpdatePaymentMethods; use App\PaymentDrivers\Stripe\UpdatePaymentMethods;
use App\PaymentDrivers\Stripe\Utilities; use App\PaymentDrivers\Stripe\Utilities;
use App\PaymentDrivers\Stripe\iDeal;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Exception; use Exception;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
@ -472,9 +473,6 @@ class StripePaymentDriver extends BaseDriver
$response = null; $response = null;
try { try {
// $response = $this->stripe
// ->refunds
// ->create(['charge' => $payment->transaction_reference, 'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision, $this->client->currency())], $meta);
$response = \Stripe\Refund::create([ $response = \Stripe\Refund::create([
'charge' => $payment->transaction_reference, 'charge' => $payment->transaction_reference,
@ -532,7 +530,15 @@ class StripePaymentDriver extends BaseDriver
// Allow app to catch up with webhook request. // Allow app to catch up with webhook request.
sleep(2); sleep(2);
if ($request->type === 'charge.succeeded' || $request->type === 'payment_intent.succeeded') { //payment_intent.succeeded - this will confirm or cancel the payment
if($request->type === 'payment_intent.succeeded'){
PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(10);
// PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id);
return response()->json([], 200);
}
if ($request->type === 'charge.succeeded') {
// if ($request->type === 'charge.succeeded' || $request->type === 'payment_intent.succeeded') {
foreach ($request->data as $transaction) { foreach ($request->data as $transaction) {
@ -559,6 +565,7 @@ class StripePaymentDriver extends BaseDriver
$payment->save(); $payment->save();
} }
} }
} elseif ($request->type === 'source.chargeable') { } elseif ($request->type === 'source.chargeable') {
$this->init(); $this->init();

View File

@ -174,15 +174,17 @@ class Statement
$item->tax_rate1 = 5; $item->tax_rate1 = 5;
} }
$product = Product::first(); //$product = Product::first();
$item->cost = (float) $product->cost; $product = new \stdClass;
$item->product_key = $product->product_key;
$item->notes = $product->notes; $item->cost = (float) 10;
$item->custom_value1 = $product->custom_value1; $item->product_key = 'test';
$item->custom_value2 = $product->custom_value2; $item->notes = 'test notes';
$item->custom_value3 = $product->custom_value3; $item->custom_value1 = 'custom value1';
$item->custom_value4 = $product->custom_value4; $item->custom_value2 = 'custom value2';
$item->custom_value3 = 'custom value3';
$item->custom_value4 = 'custom value4';
$line_items[] = $item; $line_items[] = $item;
} }

View File

@ -108,6 +108,7 @@ class CreditService
$this->updateBalance($adjustment) $this->updateBalance($adjustment)
->updatePaidToDate($adjustment) ->updatePaidToDate($adjustment)
->setStatus(Credit::STATUS_APPLIED)
->save(); ->save();
//create a negative payment of total $this->credit->balance //create a negative payment of total $this->credit->balance
@ -136,7 +137,6 @@ class CreditService
->client ->client
->service() ->service()
->updatePaidToDate($adjustment) ->updatePaidToDate($adjustment)
->setStatus(Credit::STATUS_APPLIED)
->save(); ->save();
event('eloquent.created: App\Models\Payment', $payment); event('eloquent.created: App\Models\Payment', $payment);

View File

@ -55,6 +55,16 @@ class UpdateInvoicePayment
if($paid_amount > $invoice->partial && $paid_amount > $invoice->balance) if($paid_amount > $invoice->partial && $paid_amount > $invoice->balance)
$paid_amount = $invoice->balance; $paid_amount = $invoice->balance;
/*Improve performance here - 26-01-2022 - also change the order of events for invoice first*/
$invoice->service() //caution what if we amount paid was less than partial - we wipe it!
->clearPartial()
->updateBalance($paid_amount * -1)
->updatePaidToDate($paid_amount)
->updateStatus()
->touchPdf()
->workFlow()
->save();
/* Updates the company ledger */ /* Updates the company ledger */
$this->payment $this->payment
->ledger() ->ledger()
@ -77,19 +87,22 @@ class UpdateInvoicePayment
$this->payment->applied += $paid_amount; $this->payment->applied += $paid_amount;
$invoice->service() //caution what if we amount paid was less than partial - we wipe it! // $invoice->service() //caution what if we amount paid was less than partial - we wipe it!
->clearPartial() // ->clearPartial()
->updateBalance($paid_amount * -1) // ->updateBalance($paid_amount * -1)
->updatePaidToDate($paid_amount) // ->updatePaidToDate($paid_amount)
->updateStatus() // ->updateStatus()
->save(); // ->save();
// $invoice->refresh();
// $invoice->service()
// ->touchPdf(true)
// ->workFlow()
// ->save();
$invoice->refresh();
$invoice->service()
->touchPdf(true)
->workFlow()
->save();
}); });

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.3.49', 'app_version' => '5.3.51',
'app_tag' => '5.3.49', 'app_tag' => '5.3.51',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -13,7 +13,7 @@
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::group(['middleware' => ['throttle:10,1', 'api_secret_check']], function () { Route::group(['middleware' => ['throttle:300,1', 'api_secret_check']], function () {
Route::post('api/v1/signup', 'AccountController@store')->name('signup.submit'); Route::post('api/v1/signup', 'AccountController@store')->name('signup.submit');
Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin'); Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin');
}); });

View File

@ -28,7 +28,7 @@ Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('
Route::get('client/payment/{contact_key}/{payment_id}', 'ClientPortal\InvitationController@paymentRouter')->middleware(['domain_db','contact_key_login']); Route::get('client/payment/{contact_key}/{payment_id}', 'ClientPortal\InvitationController@paymentRouter')->middleware(['domain_db','contact_key_login']);
Route::get('client/ninja/{contact_key}/{company_key}', 'ClientPortal\NinjaPlanController@index')->name('client.ninja_contact_login')->middleware(['domain_db']); Route::get('client/ninja/{contact_key}/{company_key}', 'ClientPortal\NinjaPlanController@index')->name('client.ninja_contact_login')->middleware(['domain_db']);
Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence','domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () { Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
Route::get('plan', 'ClientPortal\NinjaPlanController@plan')->name('plan'); // name = (dashboard. index / create / show / update / destroy / edit Route::get('plan', 'ClientPortal\NinjaPlanController@plan')->name('plan'); // name = (dashboard. index / create / show / update / destroy / edit