2021-03-18 01:58:10 +01:00
|
|
|
<?php
|
2021-03-18 01:53:08 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
|
|
|
|
namespace App\Http\Livewire;
|
|
|
|
|
|
|
|
use App\Factory\ClientFactory;
|
2021-03-31 18:10:44 +02:00
|
|
|
use App\Jobs\Mail\NinjaMailerJob;
|
|
|
|
use App\Jobs\Mail\NinjaMailerObject;
|
|
|
|
use App\Mail\ContactPasswordlessLogin;
|
2021-04-05 10:43:25 +02:00
|
|
|
use App\Models\Client;
|
2021-03-25 11:55:59 +01:00
|
|
|
use App\Models\Subscription;
|
2021-03-18 01:58:10 +01:00
|
|
|
use App\Models\ClientContact;
|
2021-03-18 14:14:10 +01:00
|
|
|
use App\Models\Invoice;
|
2021-03-18 01:58:10 +01:00
|
|
|
use App\Repositories\ClientContactRepository;
|
|
|
|
use App\Repositories\ClientRepository;
|
2021-03-22 14:09:29 +01:00
|
|
|
use Illuminate\Support\Facades\App;
|
2021-03-18 01:58:10 +01:00
|
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
2021-03-18 16:01:13 +01:00
|
|
|
use Illuminate\Support\Facades\DB;
|
2021-03-18 01:58:10 +01:00
|
|
|
use Livewire\Component;
|
|
|
|
|
|
|
|
class BillingPortalPurchase extends Component
|
|
|
|
{
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Random hash generated by backend to handle the tracking of state.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $hash;
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Top level text on the left side of billing page.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
public $heading_text;
|
2021-03-18 01:58:10 +01:00
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* E-mail address model for user input.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $email;
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Password model for user input.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $password;
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
2021-03-25 11:55:59 +01:00
|
|
|
* Instance of subscription.
|
2021-03-18 14:14:10 +01:00
|
|
|
*
|
2021-03-25 11:55:59 +01:00
|
|
|
* @var Subscription
|
2021-03-18 14:14:10 +01:00
|
|
|
*/
|
2021-03-25 11:55:59 +01:00
|
|
|
public $subscription;
|
2021-03-18 01:58:10 +01:00
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Instance of client contact.
|
|
|
|
*
|
|
|
|
* @var null|ClientContact
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $contact;
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Rules for validating the form.
|
|
|
|
*
|
|
|
|
* @var \string[][]
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
protected $rules = [
|
|
|
|
'email' => ['required', 'email'],
|
|
|
|
];
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Id for CompanyGateway record.
|
|
|
|
*
|
|
|
|
* @var string|integer
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $company_gateway_id;
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Id for GatewayType.
|
|
|
|
*
|
|
|
|
* @var string|integer
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $payment_method_id;
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* List of steps that frontend form follows.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $steps = [
|
|
|
|
'passed_email' => false,
|
|
|
|
'existing_user' => false,
|
|
|
|
'fetched_payment_methods' => false,
|
|
|
|
'fetched_client' => false,
|
2021-03-18 14:14:10 +01:00
|
|
|
'show_start_trial' => false,
|
2021-03-31 18:10:44 +02:00
|
|
|
'passwordless_login_sent' => false,
|
2021-04-01 16:09:30 +02:00
|
|
|
'started_payment' => false,
|
2021-04-01 17:02:11 +02:00
|
|
|
'discount_applied' => false,
|
2021-03-18 01:58:10 +01:00
|
|
|
];
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* List of payment methods fetched from client.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $methods = [];
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Instance of \App\Models\Invoice
|
|
|
|
*
|
|
|
|
* @var Invoice
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $invoice;
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Coupon model for user input
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public $coupon;
|
|
|
|
|
2021-03-18 15:22:22 +01:00
|
|
|
/**
|
|
|
|
* Quantity for seats
|
|
|
|
*
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
public $quantity = 1;
|
|
|
|
|
2021-03-18 15:41:26 +01:00
|
|
|
/**
|
|
|
|
* First-hit request data (queries, locales...).
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
public $request_data;
|
|
|
|
|
2021-03-29 15:50:36 +02:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
public $price;
|
|
|
|
|
2021-03-31 18:10:44 +02:00
|
|
|
/**
|
|
|
|
* Disabled state of passwordless login button.
|
|
|
|
*
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
public $passwordless_login_btn = false;
|
|
|
|
|
2021-03-29 15:50:36 +02:00
|
|
|
public function mount()
|
|
|
|
{
|
2021-04-01 16:04:22 +02:00
|
|
|
$this->price = $this->subscription->price;
|
2021-04-01 16:20:31 +02:00
|
|
|
|
|
|
|
if (request()->query('coupon')) {
|
|
|
|
$this->coupon = request()->query('coupon');
|
|
|
|
$this->handleCoupon();
|
|
|
|
}
|
2021-03-29 15:50:36 +02:00
|
|
|
}
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Handle user authentication
|
|
|
|
*
|
|
|
|
* @return $this|bool|void
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public function authenticate()
|
|
|
|
{
|
|
|
|
$this->validate();
|
|
|
|
|
|
|
|
$contact = ClientContact::where('email', $this->email)->first();
|
|
|
|
|
|
|
|
if ($contact && $this->steps['existing_user'] === false) {
|
|
|
|
return $this->steps['existing_user'] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($contact && $this->steps['existing_user']) {
|
|
|
|
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password]);
|
|
|
|
|
|
|
|
return $attempt
|
|
|
|
? $this->getPaymentMethods($contact)
|
|
|
|
: session()->flash('message', 'These credentials do not match our records.');
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->steps['existing_user'] = false;
|
|
|
|
|
|
|
|
$contact = $this->createBlankClient();
|
|
|
|
|
|
|
|
if ($contact && $contact instanceof ClientContact) {
|
|
|
|
$this->getPaymentMethods($contact);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Create a blank client. Used for new customers purchasing.
|
|
|
|
*
|
|
|
|
* @return mixed
|
|
|
|
* @throws \Laracasts\Presenter\Exceptions\PresenterException
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
protected function createBlankClient()
|
|
|
|
{
|
2021-03-25 11:55:59 +01:00
|
|
|
$company = $this->subscription->company;
|
|
|
|
$user = $this->subscription->user;
|
2021-03-18 01:58:10 +01:00
|
|
|
|
|
|
|
$client_repo = new ClientRepository(new ClientContactRepository());
|
|
|
|
|
2021-03-18 16:01:13 +01:00
|
|
|
$data = [
|
2021-03-18 01:58:10 +01:00
|
|
|
'name' => 'Client Name',
|
|
|
|
'contacts' => [
|
|
|
|
['email' => $this->email],
|
2021-03-18 16:01:13 +01:00
|
|
|
],
|
|
|
|
'settings' => [],
|
|
|
|
];
|
|
|
|
|
2021-04-05 10:43:25 +02:00
|
|
|
foreach ($this->request_data as $field => $value) {
|
|
|
|
if (in_array($field, Client::$subscriptions_fillable)) {
|
|
|
|
$data[$field] = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_array($field, ClientContact::$subscription_fillable)) {
|
|
|
|
$data['contacts'][0][$field] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-18 16:01:13 +01:00
|
|
|
if (array_key_exists('locale', $this->request_data)) {
|
2021-03-22 14:09:29 +01:00
|
|
|
$request = $this->request_data;
|
|
|
|
|
|
|
|
$record = Cache::get('languages')->filter(function ($item) use ($request) {
|
|
|
|
return $item->locale == $request['locale'];
|
|
|
|
})->first();
|
2021-03-18 16:01:13 +01:00
|
|
|
|
|
|
|
if ($record) {
|
|
|
|
$data['settings']['language_id'] = (string)$record->id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
|
2021-03-18 01:58:10 +01:00
|
|
|
|
|
|
|
return $client->contacts->first();
|
|
|
|
}
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Fetching payment methods from the client.
|
|
|
|
*
|
|
|
|
* @param ClientContact $contact
|
|
|
|
* @return $this
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
protected function getPaymentMethods(ClientContact $contact): self
|
|
|
|
{
|
2021-03-29 12:25:29 +02:00
|
|
|
Auth::guard('contact')->login($contact);
|
|
|
|
|
|
|
|
$this->contact = $contact;
|
|
|
|
|
2021-03-25 11:55:59 +01:00
|
|
|
if ($this->subscription->trial_enabled) {
|
2021-03-18 14:14:10 +01:00
|
|
|
$this->heading_text = ctrans('texts.plan_trial');
|
|
|
|
$this->steps['show_start_trial'] = true;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2021-03-18 01:58:10 +01:00
|
|
|
$this->steps['fetched_payment_methods'] = true;
|
|
|
|
|
|
|
|
$this->methods = $contact->client->service()->getPaymentMethods(1000);
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
$this->heading_text = ctrans('texts.payment_methods');
|
2021-03-18 01:58:10 +01:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Middle method between selecting payment method &
|
|
|
|
* submitting the from to the backend.
|
|
|
|
*
|
|
|
|
* @param $company_gateway_id
|
|
|
|
* @param $gateway_type_id
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id)
|
|
|
|
{
|
|
|
|
$this->company_gateway_id = $company_gateway_id;
|
|
|
|
$this->payment_method_id = $gateway_type_id;
|
|
|
|
|
|
|
|
$this->handleBeforePaymentEvents();
|
|
|
|
}
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Method to handle events before payments.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2021-03-18 01:58:10 +01:00
|
|
|
public function handleBeforePaymentEvents()
|
|
|
|
{
|
2021-04-01 16:09:30 +02:00
|
|
|
$this->steps['started_payment'] = true;
|
|
|
|
|
2021-03-18 01:58:10 +01:00
|
|
|
$data = [
|
|
|
|
'client_id' => $this->contact->client->id,
|
|
|
|
'date' => now()->format('Y-m-d'),
|
|
|
|
'invitations' => [[
|
|
|
|
'key' => '',
|
|
|
|
'client_contact_id' => $this->contact->hashed_id,
|
|
|
|
]],
|
|
|
|
'user_input_promo_code' => $this->coupon,
|
2021-03-25 11:55:59 +01:00
|
|
|
'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon,
|
2021-03-18 15:22:22 +01:00
|
|
|
'quantity' => $this->quantity,
|
2021-03-18 01:58:10 +01:00
|
|
|
];
|
|
|
|
|
2021-03-25 11:55:59 +01:00
|
|
|
$this->invoice = $this->subscription
|
2021-03-18 01:58:10 +01:00
|
|
|
->service()
|
|
|
|
->createInvoice($data)
|
|
|
|
->service()
|
|
|
|
->markSent()
|
2021-03-22 22:24:05 +01:00
|
|
|
->fillDefaults()
|
2021-03-18 01:58:10 +01:00
|
|
|
->save();
|
|
|
|
|
|
|
|
Cache::put($this->hash, [
|
2021-03-31 18:10:44 +02:00
|
|
|
'subscription_id' => $this->subscription->id,
|
|
|
|
'email' => $this->email ?? $this->contact->email,
|
|
|
|
'client_id' => $this->contact->client->id,
|
|
|
|
'invoice_id' => $this->invoice->id,
|
|
|
|
now()->addMinutes(60)]
|
2021-03-18 01:58:10 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
$this->emit('beforePaymentEventsCompleted');
|
|
|
|
}
|
|
|
|
|
2021-03-18 14:14:10 +01:00
|
|
|
/**
|
|
|
|
* Proxy method for starting the trial.
|
|
|
|
*
|
|
|
|
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
|
|
|
*/
|
|
|
|
public function handleTrial()
|
|
|
|
{
|
2021-03-25 11:55:59 +01:00
|
|
|
return $this->subscription->service()->startTrial([
|
2021-03-18 14:14:10 +01:00
|
|
|
'email' => $this->email ?? $this->contact->email,
|
2021-03-22 14:03:32 +01:00
|
|
|
'quantity' => $this->quantity,
|
|
|
|
'contact_id' => $this->contact->id,
|
2021-03-18 14:14:10 +01:00
|
|
|
]);
|
|
|
|
}
|
2021-03-18 02:00:01 +01:00
|
|
|
|
2021-03-18 15:22:22 +01:00
|
|
|
/**
|
|
|
|
* Update quantity property.
|
|
|
|
*
|
|
|
|
* @param string $option
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function updateQuantity(string $option): int
|
2021-03-18 01:58:10 +01:00
|
|
|
{
|
2021-03-18 15:22:22 +01:00
|
|
|
if ($this->quantity == 1 && $option == 'decrement') {
|
|
|
|
return $this->quantity;
|
|
|
|
}
|
|
|
|
|
2021-03-25 11:55:59 +01:00
|
|
|
if ($this->quantity >= $this->subscription->max_seats_limit && $option == 'increment') {
|
2021-03-18 15:22:22 +01:00
|
|
|
return $this->quantity;
|
|
|
|
}
|
|
|
|
|
2021-03-22 13:53:34 +01:00
|
|
|
if ($option == 'increment') {
|
|
|
|
$this->quantity++;
|
2021-03-25 11:55:59 +01:00
|
|
|
return $this->price = (int)$this->price + $this->subscription->product->price;
|
2021-03-22 13:53:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->quantity--;
|
2021-03-25 11:55:59 +01:00
|
|
|
$this->price = (int)$this->price - $this->subscription->product->price;
|
2021-03-22 13:53:34 +01:00
|
|
|
|
|
|
|
return 0;
|
2021-03-18 01:58:10 +01:00
|
|
|
}
|
|
|
|
|
2021-03-29 15:50:36 +02:00
|
|
|
public function handleCoupon()
|
|
|
|
{
|
|
|
|
if ($this->coupon == $this->subscription->promo_code) {
|
|
|
|
$this->price = $this->subscription->promo_price;
|
2021-04-01 17:02:11 +02:00
|
|
|
$this->steps['discount_applied'] = true;
|
2021-03-29 15:50:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-31 18:10:44 +02:00
|
|
|
public function passwordlessLogin()
|
|
|
|
{
|
|
|
|
$this->passwordless_login_btn = true;
|
|
|
|
|
|
|
|
$contact = ClientContact::query()
|
|
|
|
->where('email', $this->email)
|
|
|
|
->first();
|
|
|
|
|
|
|
|
$mailer = new NinjaMailerObject();
|
2021-04-01 16:20:31 +02:00
|
|
|
$mailer->mailable = new ContactPasswordlessLogin($this->email, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon);
|
2021-03-31 18:10:44 +02:00
|
|
|
$mailer->company = $this->subscription->company;
|
|
|
|
$mailer->settings = $this->subscription->company->settings;
|
|
|
|
$mailer->to_user = $contact;
|
|
|
|
|
|
|
|
NinjaMailerJob::dispatchNow($mailer);
|
|
|
|
|
|
|
|
$this->steps['passwordless_login_sent'] = true;
|
|
|
|
$this->passwordless_login_btn = false;
|
|
|
|
}
|
|
|
|
|
2021-03-18 01:58:10 +01:00
|
|
|
public function render()
|
|
|
|
{
|
2021-04-05 10:43:25 +02:00
|
|
|
if (array_key_exists('email', $this->request_data)) {
|
|
|
|
$this->email = $this->request_data['email'];
|
|
|
|
}
|
|
|
|
|
2021-03-18 01:58:10 +01:00
|
|
|
if ($this->contact instanceof ClientContact) {
|
|
|
|
$this->getPaymentMethods($this->contact);
|
|
|
|
}
|
|
|
|
|
|
|
|
return render('components.livewire.billing-portal-purchase');
|
|
|
|
}
|
|
|
|
}
|