mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #5316 from beganovich/v5-0104-billing-portal
V5 0104 billing portal
This commit is contained in:
commit
698647cb96
@ -61,27 +61,28 @@ class RecurringInvoiceController extends Controller
|
|||||||
|
|
||||||
public function requestCancellation(Request $request, RecurringInvoice $recurring_invoice)
|
public function requestCancellation(Request $request, RecurringInvoice $recurring_invoice)
|
||||||
{
|
{
|
||||||
//todo double check the user is able to request a cancellation
|
if (is_null($recurring_invoice->subscription_id) || optional($recurring_invoice->subscription)->allow_cancellation) {
|
||||||
//can add locale specific by chaining ->locale();
|
$nmo = new NinjaMailerObject;
|
||||||
|
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->user()))->build()));
|
||||||
$nmo = new NinjaMailerObject;
|
$nmo->company = $recurring_invoice->company;
|
||||||
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->user()))->build()));
|
$nmo->settings = $recurring_invoice->company->settings;
|
||||||
$nmo->company = $recurring_invoice->company;
|
|
||||||
$nmo->settings = $recurring_invoice->company->settings;
|
|
||||||
|
|
||||||
$notifiable_users = $this->filterUsersByPermissions($recurring_invoice->company->company_users, $recurring_invoice, ['recurring_cancellation']);
|
$notifiable_users = $this->filterUsersByPermissions($recurring_invoice->company->company_users, $recurring_invoice, ['recurring_cancellation']);
|
||||||
|
|
||||||
$notifiable_users->each(function ($company_user) use($nmo){
|
$notifiable_users->each(function ($company_user) use($nmo){
|
||||||
|
|
||||||
$nmo->to_user = $company_user->user;
|
$nmo->to_user = $company_user->user;
|
||||||
NinjaMailerJob::dispatch($nmo);
|
NinjaMailerJob::dispatch($nmo);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//$recurring_invoice->user->notify(new ClientContactRequestCancellation($recurring_invoice, auth()->user()));
|
//$recurring_invoice->user->notify(new ClientContactRequestCancellation($recurring_invoice, auth()->user()));
|
||||||
|
|
||||||
return $this->render('recurring_invoices.cancellation.index', [
|
return $this->render('recurring_invoices.cancellation.index', [
|
||||||
'invoice' => $recurring_invoice,
|
'invoice' => $recurring_invoice,
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,7 @@ class BillingPortalPurchase extends Component
|
|||||||
'fetched_client' => false,
|
'fetched_client' => false,
|
||||||
'show_start_trial' => false,
|
'show_start_trial' => false,
|
||||||
'passwordless_login_sent' => false,
|
'passwordless_login_sent' => false,
|
||||||
|
'started_payment' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,7 +158,12 @@ class BillingPortalPurchase extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->price = $this->subscription->service()->price();
|
$this->price = $this->subscription->price;
|
||||||
|
|
||||||
|
if (request()->query('coupon')) {
|
||||||
|
$this->coupon = request()->query('coupon');
|
||||||
|
$this->handleCoupon();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -280,6 +286,8 @@ class BillingPortalPurchase extends Component
|
|||||||
*/
|
*/
|
||||||
public function handleBeforePaymentEvents()
|
public function handleBeforePaymentEvents()
|
||||||
{
|
{
|
||||||
|
$this->steps['started_payment'] = true;
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'client_id' => $this->contact->client->id,
|
'client_id' => $this->contact->client->id,
|
||||||
'date' => now()->format('Y-m-d'),
|
'date' => now()->format('Y-m-d'),
|
||||||
@ -368,7 +376,7 @@ class BillingPortalPurchase extends Component
|
|||||||
->first();
|
->first();
|
||||||
|
|
||||||
$mailer = new NinjaMailerObject();
|
$mailer = new NinjaMailerObject();
|
||||||
$mailer->mailable = new ContactPasswordlessLogin($this->email, (string)route('client.subscription.purchase', $this->subscription->hashed_id));
|
$mailer->mailable = new ContactPasswordlessLogin($this->email, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon);
|
||||||
$mailer->company = $this->subscription->company;
|
$mailer->company = $this->subscription->company;
|
||||||
$mailer->settings = $this->subscription->company->settings;
|
$mailer->settings = $this->subscription->company->settings;
|
||||||
$mailer->to_user = $contact;
|
$mailer->to_user = $contact;
|
||||||
|
@ -196,7 +196,7 @@ class RecurringInvoice extends BaseModel
|
|||||||
{
|
{
|
||||||
return $this->morphMany(Document::class, 'documentable');
|
return $this->morphMany(Document::class, 'documentable');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStatusAttribute()
|
public function getStatusAttribute()
|
||||||
{
|
{
|
||||||
if ($this->status_id == self::STATUS_ACTIVE && Carbon::parse($this->next_send_date)->isFuture()) {
|
if ($this->status_id == self::STATUS_ACTIVE && Carbon::parse($this->next_send_date)->isFuture()) {
|
||||||
@ -394,9 +394,9 @@ class RecurringInvoice extends BaseModel
|
|||||||
if ($this->remaining_cycles == -1) {
|
if ($this->remaining_cycles == -1) {
|
||||||
$iterations = 10;
|
$iterations = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
if (!Carbon::parse($this->next_send_date)) {
|
if (!Carbon::parse($this->next_send_date)) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
@ -407,7 +407,7 @@ class RecurringInvoice extends BaseModel
|
|||||||
// we don't add the days... we calc the day of the month!!
|
// we don't add the days... we calc the day of the month!!
|
||||||
$next_due_date = $this->calculateDueDate($next_send_date->copy()->format('Y-m-d'));
|
$next_due_date = $this->calculateDueDate($next_send_date->copy()->format('Y-m-d'));
|
||||||
$next_due_date_string = $next_due_date ? $next_due_date->format('Y-m-d') : '';
|
$next_due_date_string = $next_due_date ? $next_due_date->format('Y-m-d') : '';
|
||||||
|
|
||||||
$next_send_date = Carbon::parse($next_send_date);
|
$next_send_date = Carbon::parse($next_send_date);
|
||||||
|
|
||||||
$data[] = [
|
$data[] = [
|
||||||
@ -420,7 +420,7 @@ class RecurringInvoice extends BaseModel
|
|||||||
|
|
||||||
/*If no due date is set - unset the due_date value */
|
/*If no due date is set - unset the due_date value */
|
||||||
// if(!$this->due_date_days || $this->due_date_days == 0){
|
// if(!$this->due_date_days || $this->due_date_days == 0){
|
||||||
|
|
||||||
// foreach($data as $key => $value)
|
// foreach($data as $key => $value)
|
||||||
// $data[$key]['due_date'] = '';
|
// $data[$key]['due_date'] = '';
|
||||||
|
|
||||||
@ -468,4 +468,9 @@ class RecurringInvoice extends BaseModel
|
|||||||
{
|
{
|
||||||
return new RecurringService($this);
|
return new RecurringService($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function subscription(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Subscription::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,14 +111,14 @@ class SubscriptionService
|
|||||||
$recurring_invoice->next_send_date = now()->addSeconds($this->subscription->trial_duration);
|
$recurring_invoice->next_send_date = now()->addSeconds($this->subscription->trial_duration);
|
||||||
$recurring_invoice->backup = 'is_trial';
|
$recurring_invoice->backup = 'is_trial';
|
||||||
|
|
||||||
if(array_key_exists('coupon', $data) && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
if(array_key_exists('coupon', $data) && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
||||||
{
|
{
|
||||||
$recurring_invoice->discount = $this->subscription->promo_discount;
|
$recurring_invoice->discount = $this->subscription->promo_discount;
|
||||||
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
$recurring_invoice = $recurring_invoice_repo->save($data, $recurring_invoice);
|
$recurring_invoice = $recurring_invoice_repo->save($data, $recurring_invoice);
|
||||||
|
|
||||||
/* Start the recurring service */
|
/* Start the recurring service */
|
||||||
$recurring_invoice->service()
|
$recurring_invoice->service()
|
||||||
->start()
|
->start()
|
||||||
@ -144,7 +144,7 @@ class SubscriptionService
|
|||||||
$invoice->line_items = $subscription_repo->generateLineItems($this->subscription);
|
$invoice->line_items = $subscription_repo->generateLineItems($this->subscription);
|
||||||
$invoice->subscription_id = $this->subscription->id;
|
$invoice->subscription_id = $this->subscription->id;
|
||||||
|
|
||||||
if(strlen($data['coupon']) >=1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
if(strlen($data['coupon']) >=1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
||||||
{
|
{
|
||||||
$invoice->discount = $this->subscription->promo_discount;
|
$invoice->discount = $this->subscription->promo_discount;
|
||||||
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||||
@ -161,7 +161,7 @@ class SubscriptionService
|
|||||||
$subscription_repo = new SubscriptionRepository();
|
$subscription_repo = new SubscriptionRepository();
|
||||||
|
|
||||||
$recurring_invoice = RecurringInvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
$recurring_invoice = RecurringInvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
||||||
$recurring_invoice->client_id = $client_id;
|
$recurring_invoice->client_id = $client_id;
|
||||||
$recurring_invoice->line_items = $subscription_repo->generateLineItems($this->subscription, true);
|
$recurring_invoice->line_items = $subscription_repo->generateLineItems($this->subscription, true);
|
||||||
$recurring_invoice->subscription_id = $this->subscription->id;
|
$recurring_invoice->subscription_id = $this->subscription->id;
|
||||||
$recurring_invoice->frequency_id = $this->subscription->frequency_id ?: RecurringInvoice::FREQUENCY_MONTHLY;
|
$recurring_invoice->frequency_id = $this->subscription->frequency_id ?: RecurringInvoice::FREQUENCY_MONTHLY;
|
||||||
@ -249,10 +249,4 @@ class SubscriptionService
|
|||||||
{
|
{
|
||||||
return Product::whereIn('id', $this->transformKeys(explode(",", $this->subscription->recurring_product_ids)))->get();
|
return Product::whereIn('id', $this->transformKeys(explode(",", $this->subscription->recurring_product_ids)))->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function price()
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
@include('email.components.header', ['logo' => 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png'])
|
@include('email.components.header', ['logo' => 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png'])
|
||||||
@endslot
|
@endslot
|
||||||
|
|
||||||
<h2>Passwordless login link requested</h2>
|
<h2>Login link requested</h2>
|
||||||
<p>Hey, there was a request to log in using passwordless link.</p>
|
<p>Hey, there was a request to log in using link.</p>
|
||||||
|
|
||||||
<a href="{{ $url }}" target="_blank" class="button">Sign in to Invoice Ninja</a>
|
<a href="{{ $url }}" target="_blank" class="button">Sign in to Invoice Ninja</a>
|
||||||
|
|
||||||
|
@ -5,20 +5,6 @@
|
|||||||
alt="{{ $subscription->company->present()->name }}">
|
alt="{{ $subscription->company->present()->name }}">
|
||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
@if(!empty($subscription->product_ids))
|
|
||||||
<p
|
|
||||||
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
|
||||||
One-time purchase
|
|
||||||
</p>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if(!empty($subscription->recurring_product_ids))
|
|
||||||
<p
|
|
||||||
class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
|
||||||
Subscription
|
|
||||||
</p>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<h1 id="billing-page-company-logo" class="text-3xl font-bold tracking-wide">
|
<h1 id="billing-page-company-logo" class="text-3xl font-bold tracking-wide">
|
||||||
{{ $subscription->name }}
|
{{ $subscription->name }}
|
||||||
</h1>
|
</h1>
|
||||||
@ -71,7 +57,8 @@
|
|||||||
|
|
||||||
<div class="relative flex justify-center text-sm leading-5">
|
<div class="relative flex justify-center text-sm leading-5">
|
||||||
<h1 class="text-2xl font-bold tracking-wide bg-gray-50 px-6 py-0">
|
<h1 class="text-2xl font-bold tracking-wide bg-gray-50 px-6 py-0">
|
||||||
{{ ctrans('texts.total') }}: {{ \App\Utils\Number::formatMoney($price, $subscription->company) }}
|
{{ ctrans('texts.total') }}
|
||||||
|
: {{ \App\Utils\Number::formatMoney($price, $subscription->company) }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -121,13 +108,25 @@
|
|||||||
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}"/>
|
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@foreach($this->methods as $method)
|
@if($steps['started_payment'] == false)
|
||||||
<button
|
@foreach($this->methods as $method)
|
||||||
wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}')"
|
<button
|
||||||
class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
|
wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}')"
|
||||||
{{ $method['label'] }}
|
class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
|
||||||
</button>
|
{{ $method['label'] }}
|
||||||
@endforeach
|
</button>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($steps['started_payment'])
|
||||||
|
<svg class="animate-spin h-8 w-8 text-primary" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
|
||||||
|
stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@elseif($steps['show_start_trial'])
|
@elseif($steps['show_start_trial'])
|
||||||
<form wire:submit.prevent="handleTrial" class="mt-8">
|
<form wire:submit.prevent="handleTrial" class="mt-8">
|
||||||
@ -167,12 +166,14 @@
|
|||||||
@enderror
|
@enderror
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button wire:loading.attr="disabled" type="button" wire:click="passwordlessLogin" class="mt-4 text-sm active:outline-none focus:outline-none">
|
<button wire:loading.attr="disabled" type="button" wire:click="passwordlessLogin"
|
||||||
|
class="mt-4 text-sm active:outline-none focus:outline-none">
|
||||||
Log in without password
|
Log in without password
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@if($steps['passwordless_login_sent'])
|
@if($steps['passwordless_login_sent'])
|
||||||
<span class="block mt-2 text-sm text-green-600">E-mail sent. Please check your inbox!</span>
|
<span
|
||||||
|
class="block mt-2 text-sm text-green-600">E-mail sent. Please check your inbox!</span>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@ -55,31 +55,35 @@
|
|||||||
{{ \App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
{{ \App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white shadow sm:rounded-lg mb-4 mt-4">
|
@if(is_null($invoice->subscription_id) || optional($invoice->subscription)->allow_cancellation)
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="bg-white shadow sm:rounded-lg mb-4 mt-4">
|
||||||
<div class="sm:flex sm:items-start sm:justify-between">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<div>
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
<div>
|
||||||
{{ ctrans('texts.cancellation') }}
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
</h3>
|
{{ ctrans('texts.cancellation') }}
|
||||||
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
</h3>
|
||||||
<p translate>
|
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||||
{{ ctrans('texts.about_cancellation') }}
|
<p translate>
|
||||||
</p>
|
{{ ctrans('texts.about_cancellation') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
||||||
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
<button class="button button-danger" translate @click="open = true">Request Cancellation
|
||||||
<button class="button button-danger" translate @click="open = true">Request Cancellation
|
</button>
|
||||||
</button>
|
@include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
|
||||||
@include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
Loading…
x
Reference in New Issue
Block a user