1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 21:22:58 +01:00
invoiceninja/app/Http/Livewire/BillingPortalPurchasev2.php

773 lines
21 KiB
PHP
Raw Normal View History

2022-11-23 07:27:43 +01:00
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Livewire;
2022-12-07 06:45:25 +01:00
use App\DataMapper\ClientSettings;
2022-11-23 07:27:43 +01:00
use App\Factory\ClientFactory;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\ContactPasswordlessLogin;
2022-12-10 02:28:54 +01:00
use App\Mail\Subscription\OtpCode;
2022-11-23 07:27:43 +01:00
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Invoice;
2022-12-07 06:45:25 +01:00
use App\Models\RecurringInvoice;
2022-11-23 07:27:43 +01:00
use App\Models\Subscription;
use App\Repositories\ClientContactRepository;
use App\Repositories\ClientRepository;
2022-12-07 06:45:25 +01:00
use App\Utils\Number;
2022-11-23 07:27:43 +01:00
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
2022-12-07 06:45:25 +01:00
use Illuminate\Support\Facades\Validator;
2022-11-23 07:27:43 +01:00
use Illuminate\Support\Str;
2022-12-09 09:39:27 +01:00
use Illuminate\Validation\Rule;
2022-11-23 07:27:43 +01:00
use Livewire\Component;
class BillingPortalPurchasev2 extends Component
{
/**
* Random hash generated by backend to handle the tracking of state.
*
* @var string
*/
public $hash;
/**
* E-mail address model for user input.
*
* @var string
*/
public $email;
/**
* Password model for user input.
*
* @var string
*/
public $password;
/**
* Instance of subscription.
*
* @var Subscription
*/
public $subscription;
/**
* Instance of client contact.
*
* @var null|ClientContact
*/
public $contact;
/**
* Rules for validating the form.
*
* @var \string[][]
*/
2022-12-01 20:59:16 +01:00
// protected $rules = [
// 'email' => ['required', 'email'],
// 'data' => ['required', 'array'],
// 'data.*.recurring_qty' => ['required', 'between:100,1000'],
// 'data.*.optional_recurring_qty' => ['required', 'between:100,1000'],
// 'data.*.optional_qty' => ['required', 'between:100,1000'],
// ];
2022-11-23 07:27:43 +01:00
/**
* Id for CompanyGateway record.
*
* @var string|integer
*/
public $company_gateway_id;
/**
* Id for GatewayType.
*
* @var string|integer
*/
public $payment_method_id;
/**
* List of steps that frontend form follows.
*
* @var array
*/
public $steps = [
'passed_email' => false,
'existing_user' => false,
'fetched_payment_methods' => false,
'fetched_client' => false,
'show_start_trial' => false,
'passwordless_login_sent' => false,
'started_payment' => false,
'discount_applied' => false,
'show_loading_bar' => false,
'not_eligible' => null,
'not_eligible_message' => null,
'payment_required' => true,
];
2022-12-01 20:59:16 +01:00
public $data = [];
2022-11-23 07:27:43 +01:00
/**
* List of payment methods fetched from client.
*
* @var array
*/
public $methods = [];
/**
* Instance of \App\Models\Invoice
*
* @var Invoice
*/
public $invoice;
/**
* Coupon model for user input
*
* @var string
*/
public $coupon;
/**
* Quantity for seats
*
* @var int
*/
public $quantity;
/**
* First-hit request data (queries, locales...).
*
* @var array
*/
public $request_data;
/**
* @var string
*/
public $price;
/**
* Disabled state of passwordless login button.
*
* @var bool
*/
public $passwordless_login_btn = false;
/**
* Instance of company.
*
* @var Company
*/
public $company;
/**
* Campaign reference.
*
* @var string|null
*/
public $campaign;
2022-12-07 06:45:25 +01:00
public $bundle;
public $recurring_products;
public $products;
public $optional_recurring_products;
public $optional_products;
public $total;
2022-12-09 03:47:54 +01:00
public $discount;
2022-12-09 04:36:39 +01:00
public $sub_total;
2022-12-09 05:41:54 +01:00
public $authenticated = false;
public $login;
2022-12-10 04:25:46 +01:00
public $float_amount_total;
2022-12-10 06:31:31 +01:00
public $payment_started = false;
2022-12-07 06:45:25 +01:00
2022-11-23 07:27:43 +01:00
public function mount()
{
MultiDB::setDb($this->company->db);
2022-12-09 03:47:54 +01:00
$this->discount = 0;
2022-12-09 04:36:39 +01:00
$this->sub_total = 0;
2022-12-10 04:25:46 +01:00
$this->float_amount_total = 0;
2022-11-23 07:27:43 +01:00
2022-12-01 20:59:16 +01:00
$this->data = [];
2022-12-10 06:31:31 +01:00
$this->price = $this->subscription->price; // ?
2022-12-07 06:45:25 +01:00
$this->recurring_products = $this->subscription->service()->recurring_products();
$this->products = $this->subscription->service()->products();
$this->optional_recurring_products = $this->subscription->service()->optional_recurring_products();
$this->optional_products = $this->subscription->service()->optional_products();
2022-12-07 06:48:40 +01:00
$this->bundle = collect();
2022-12-10 06:31:31 +01:00
//every thing below is redundant
if (request()->query('coupon')) {
$this->coupon = request()->query('coupon');
$this->handleCoupon();
}
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
$this->price = $this->subscription->promo_price;
}
2022-12-07 06:45:25 +01:00
2022-11-23 07:27:43 +01:00
}
2022-12-09 09:39:27 +01:00
public function loginValidation()
{
$this->resetErrorBag('login');
$this->resetValidation('login');
}
2022-12-09 05:41:54 +01:00
2022-12-10 02:28:54 +01:00
public function handleLogin($user_code)
2022-12-09 05:41:54 +01:00
{
2022-12-09 09:39:27 +01:00
2022-12-10 02:28:54 +01:00
$this->resetErrorBag('login');
$this->resetValidation('login');
2022-12-09 09:39:27 +01:00
$code = Cache::get("subscriptions:otp:{$this->email}");
2022-12-10 02:28:54 +01:00
if($user_code != $code){
$errors = $this->getErrorBag();
$errors->add('login', ctrans('texts.invalid_code'));
2022-12-10 06:31:31 +01:00
return $this;
2022-12-10 02:28:54 +01:00
}
2022-12-09 09:39:27 +01:00
$contact = ClientContact::where('email', $this->email)->first();
if($contact){
Auth::guard('contact')->loginUsingId($contact->id, true);
$this->contact = $contact;
}
else {
2022-12-10 02:28:54 +01:00
$this->createClientContact();
2022-12-09 09:39:27 +01:00
}
2022-12-10 04:25:46 +01:00
$this->authenticated = true;
$this->getPaymentMethods();
2022-12-10 02:28:54 +01:00
}
public function showClientRequiredFields()
{
2022-12-09 09:39:27 +01:00
2022-12-09 05:41:54 +01:00
}
2022-12-10 04:25:46 +01:00
public function resetEmail()
{
$this->email = null;
}
2022-12-09 05:41:54 +01:00
public function handleEmail()
{
$this->validateOnly('email', ['email' => 'required|bail|email:rfc']);
2022-12-09 06:50:48 +01:00
2022-12-09 05:41:54 +01:00
$rand = rand(100000,999999);
2022-12-09 09:39:27 +01:00
$email_hash = "subscriptions:otp:{$this->email}";
2022-12-09 05:41:54 +01:00
2022-12-09 09:39:27 +01:00
Cache::put($email_hash, $rand, 120);
2022-12-09 05:41:54 +01:00
2022-12-10 02:28:54 +01:00
$this->emailOtpCode($rand);
}
private function emailOtpCode($code)
{
$cc = new ClientContact();
$cc->email = $this->email;
$nmo = new NinjaMailerObject;
$nmo->mailable = new OtpCode($this->subscription->company, $this->contact, $code);
$nmo->company = $this->subscription->company;
$nmo->settings = $this->subscription->company->settings;
$nmo->to_user = $cc;
NinjaMailerJob::dispatch($nmo);
2022-12-09 05:41:54 +01:00
}
2022-12-10 02:28:54 +01:00
2022-12-09 04:36:39 +01:00
/**
* Handle a coupon being entered into the checkout
*/
public function handleCoupon()
{
if($this->coupon == $this->subscription->promo_code)
$this->buildBundle();
else
$this->discount = 0;
}
/**
* Build the bundle in the checkout
*/
2022-12-07 06:45:25 +01:00
public function buildBundle()
2022-12-01 20:59:16 +01:00
{
2022-12-07 06:48:40 +01:00
$this->bundle = collect();
2022-12-07 06:45:25 +01:00
$data = $this->data;
2022-12-09 04:36:39 +01:00
/* Recurring products can have a variable quantity */
2022-12-07 06:45:25 +01:00
foreach($this->recurring_products as $key => $p)
{
$qty = isset($data[$key]['recurring_qty']) ? $data[$key]['recurring_qty'] : 1;
$total = $p->price * $qty;
$this->bundle->push([
'product' => nl2br(substr($p->notes, 0, 50)),
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
'total' => $total,
'qty' => $qty,
2022-12-09 02:35:37 +01:00
'is_recurring' => true,
2022-12-07 06:45:25 +01:00
]);
}
2022-12-09 04:36:39 +01:00
/* One time products can only have a single quantity */
2022-12-07 06:45:25 +01:00
foreach($this->products as $key => $p)
{
$qty = 1;
$total = $p->price * $qty;
$this->bundle->push([
'product' => nl2br(substr($p->notes, 0, 50)),
'price' => Number::formatMoney($total, $this->subscription->company),
'total' => $total,
'qty' => $qty,
'is_recurring' => false
]);
2022-12-09 04:36:39 +01:00
2022-12-07 06:45:25 +01:00
}
foreach($this->data as $key => $value)
{
2022-12-09 04:36:39 +01:00
/* Optional recurring products can have a variable quantity */
2022-12-07 06:45:25 +01:00
if(isset($this->data[$key]['optional_recurring_qty']))
{
$p = $this->optional_recurring_products->first(function ($v,$k) use($key){
return $k == $key;
});
$qty = isset($this->data[$key]['optional_recurring_qty']) ? $this->data[$key]['optional_recurring_qty'] : 0;
$total = $p->price * $qty;
if($qty == 0)
return;
$this->bundle->push([
'product' => nl2br(substr($p->notes, 0, 50)),
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
'total' => $total,
'qty' => $qty,
'is_recurring' => true
]);
}
2022-12-09 04:36:39 +01:00
/* Optional products can have a variable quantity */
2022-12-07 06:45:25 +01:00
if(isset($this->data[$key]['optional_qty']))
{
$p = $this->optional_products->first(function ($v,$k) use($key){
return $k == $key;
});
$qty = isset($this->data[$key]['optional_qty']) ? $this->data[$key]['optional_qty'] : 0;
$total = $p->price * $qty;
if($qty == 0)
return;
$this->bundle->push([
'product' => nl2br(substr($p->notes, 0, 50)),
'price' => Number::formatMoney($total, $this->subscription->company),
'total' => $total,
'qty' => $qty,
'is_recurring' => false
]);
}
}
2022-12-09 04:36:39 +01:00
$this->sub_total = Number::formatMoney($this->bundle->sum('total'), $this->subscription->company);
$this->total = $this->sub_total;
if($this->coupon == $this->subscription->promo_code)
{
if($this->subscription->is_amount_discount)
$discount = $this->subscription->promo_discount;
else
$discount = round($this->bundle->sum('total') * ($this->subscription->promo_discount / 100), 2);
$this->discount = Number::formatMoney($discount, $this->subscription->company);
$this->total = Number::formatMoney(($this->bundle->sum('total') - $discount), $this->subscription->company);
2022-12-10 04:25:46 +01:00
$this->float_amount_total = ($this->bundle->sum('total') - $discount);
2022-12-09 04:36:39 +01:00
}
2022-12-07 06:45:25 +01:00
return $this;
2022-12-01 20:59:16 +01:00
}
2022-12-09 09:39:27 +01:00
private function createClientContact()
2022-12-01 20:59:16 +01:00
{
2022-12-09 03:47:54 +01:00
2022-12-09 09:39:27 +01:00
$company = $this->subscription->company;
$user = $this->subscription->user;
$user->setCompany($company);
2022-12-07 06:45:25 +01:00
2022-12-09 09:39:27 +01:00
$client_repo = new ClientRepository(new ClientContactRepository());
$data = [
'name' => '',
'contacts' => [
['email' => $this->email],
],
'client_hash' => Str::random(40),
'settings' => ClientSettings::defaults(),
];
2022-12-09 03:47:54 +01:00
2022-12-09 09:39:27 +01:00
$client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
$this->contact = $client->fresh()->contacts()->first();
Auth::guard('contact')->loginUsingId($this->contact->id, true);
2022-12-10 02:28:54 +01:00
return $this;
2022-12-09 09:39:27 +01:00
}
2022-12-01 20:59:16 +01:00
public function updated($propertyName)
{
2022-12-09 09:39:27 +01:00
if(in_array($propertyName, ['login','email']))
return;
2022-12-07 06:45:25 +01:00
$this->buildBundle();
2022-12-01 20:59:16 +01:00
}
2022-12-10 06:31:31 +01:00
/**
* Fetching payment methods from the client.
*
* @return $this
*/
protected function getPaymentMethods(): self
{
$this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total);
return $this;
}
/**
* Middle method between selecting payment method &
* submitting the from to the backend.
*
* @param $company_gateway_id
* @param $gateway_type_id
*/
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();
}
/**
* Method to handle events before payments.
*
* @return void
*/
public function handleBeforePaymentEvents() :void
{
$this->payment_started = true;
// $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,
// 'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon,
// // 'quantity' => $this->quantity,
// ];
// $is_eligible = $this->subscription->service()->isEligible($this->contact);
// if (is_array($is_eligible) && $is_eligible['message'] != 'Success') {
// $this->steps['not_eligible'] = true;
// $this->steps['not_eligible_message'] = $is_eligible['message'];
// $this->steps['show_loading_bar'] = false;
// return;
// }
// $this->invoice = $this->subscription
// ->service()
// ->createInvoice($data, $this->quantity)
// ->service()
// ->markSent()
// ->fillDefaults()
// ->adjustInventory()
// ->save();
// Cache::put($this->hash, [
// 'subscription_id' => $this->subscription->id,
// 'email' => $this->email ?? $this->contact->email,
// 'client_id' => $this->contact->client->id,
// 'invoice_id' => $this->invoice->id,
// 'context' => 'purchase',
// 'campaign' => $this->campaign,
// ], now()->addMinutes(60));
$this->emit('beforePaymentEventsCompleted');
}
2022-12-10 02:28:54 +01:00
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-12-01 20:59:16 +01:00
public function rules()
{
2022-12-07 06:45:25 +01:00
$rules = [
2022-12-01 20:59:16 +01:00
];
return $rules;
}
2022-12-07 06:45:25 +01:00
public function attributes()
{
$attributes = [
];
return $attributes;
}
public function store()
{
}
2022-12-10 04:25:46 +01:00
// /**
// * Handle user authentication
// *
// * @return $this|bool|void
// */
// public function authenticate()
// {
// $this->validate();
2022-11-23 07:27:43 +01:00
2022-12-10 04:25:46 +01:00
// $contact = ClientContact::where('email', $this->email)
// ->where('company_id', $this->subscription->company_id)
// ->first();
2022-11-23 07:27:43 +01:00
2022-12-10 04:25:46 +01:00
// if ($contact && $this->steps['existing_user'] === false) {
// return $this->steps['existing_user'] = true;
// }
2022-11-23 07:27:43 +01:00
2022-12-10 04:25:46 +01:00
// if ($contact && $this->steps['existing_user']) {
// $attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]);
2022-11-23 07:27:43 +01:00
2022-12-10 04:25:46 +01:00
// return $attempt
// ? $this->getPaymentMethods($contact)
// : session()->flash('message', 'These credentials do not match our records.');
// }
2022-11-23 07:27:43 +01:00
2022-12-10 04:25:46 +01:00
// $this->steps['existing_user'] = false;
2022-11-23 07:27:43 +01:00
2022-12-10 04:25:46 +01:00
// $contact = $this->createBlankClient();
2022-11-23 07:27:43 +01:00
2022-12-10 04:25:46 +01:00
// if ($contact && $contact instanceof ClientContact) {
// $this->getPaymentMethods($contact);
// }
// }
2022-11-23 07:27:43 +01:00
2022-12-09 09:39:27 +01:00
2022-11-23 07:27:43 +01:00
/**
* Create a blank client. Used for new customers purchasing.
*
* @return mixed
* @throws \Laracasts\Presenter\Exceptions\PresenterException
*/
protected function createBlankClient()
{
$company = $this->subscription->company;
$user = $this->subscription->user;
$user->setCompany($company);
$client_repo = new ClientRepository(new ClientContactRepository());
$data = [
'name' => '',
'contacts' => [
['email' => $this->email],
],
'client_hash' => Str::random(40),
'settings' => ClientSettings::defaults(),
];
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;
}
}
if(array_key_exists('currency_id', $this->request_data)) {
$currency = Cache::get('currencies')->filter(function ($item){
return $item->id == $this->request_data['currency_id'];
})->first();
if($currency)
$data['settings']->currency_id = $currency->id;
}
elseif($this->subscription->group_settings && property_exists($this->subscription->group_settings->settings, 'currency_id')) {
$currency = Cache::get('currencies')->filter(function ($item){
return $item->id == $this->subscription->group_settings->settings->currency_id;
})->first();
if($currency)
$data['settings']->currency_id = $currency->id;
}
if (array_key_exists('locale', $this->request_data)) {
$request = $this->request_data;
$record = Cache::get('languages')->filter(function ($item) use ($request) {
return $item->locale == $request['locale'];
})->first();
if ($record) {
$data['settings']['language_id'] = (string)$record->id;
}
}
$client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
return $client->fresh()->contacts->first();
}
2022-12-10 06:31:31 +01:00
2022-11-23 07:27:43 +01:00
/**
* Proxy method for starting the trial.
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function handleTrial()
{
return $this->subscription->service()->startTrial([
'email' => $this->email ?? $this->contact->email,
2022-12-09 04:36:39 +01:00
'quantity' => $this->quantity,
2022-11-23 07:27:43 +01:00
'contact_id' => $this->contact->id,
'client_id' => $this->contact->client->id,
]);
}
public function handlePaymentNotRequired()
{
$is_eligible = $this->subscription->service()->isEligible($this->contact);
if ($is_eligible['status_code'] != 200) {
$this->steps['not_eligible'] = true;
$this->steps['not_eligible_message'] = $is_eligible['message'];
$this->steps['show_loading_bar'] = false;
return;
}
return $this->subscription->service()->handleNoPaymentRequired([
'email' => $this->email ?? $this->contact->email,
'quantity' => $this->quantity,
'contact_id' => $this->contact->id,
'client_id' => $this->contact->client->id,
'coupon' => $this->coupon,
]);
}
public function passwordlessLogin()
{
$this->passwordless_login_btn = true;
$contact = ClientContact::query()
->where('email', $this->email)
->where('company_id', $this->subscription->company_id)
->first();
$mailer = new NinjaMailerObject();
$mailer->mailable = new ContactPasswordlessLogin($this->email, $this->subscription->company, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon);
$mailer->company = $this->subscription->company;
$mailer->settings = $this->subscription->company->settings;
$mailer->to_user = $contact;
NinjaMailerJob::dispatch($mailer);
$this->steps['passwordless_login_sent'] = true;
$this->passwordless_login_btn = false;
}
public function render()
{
if (array_key_exists('email', $this->request_data)) {
$this->email = $this->request_data['email'];
}
if ($this->contact instanceof ClientContact) {
$this->getPaymentMethods($this->contact);
}
return render('components.livewire.billing-portal-purchasev2');
}
2022-12-01 20:59:16 +01:00
2022-12-09 04:36:39 +01:00
// public function changeData()
// {
2022-12-01 20:59:16 +01:00
2022-12-09 04:36:39 +01:00
// nlog($this->data);
2022-12-01 20:59:16 +01:00
2022-12-09 04:36:39 +01:00
// }
2022-11-23 07:27:43 +01:00
}