mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 13:12:50 +01:00
commit
da60b4dbf0
@ -193,9 +193,7 @@ class ClientFilters extends QueryFilters
|
||||
->where('clients.company_id', '=', $company_id)
|
||||
->where('client_contacts.is_primary', '=', true)
|
||||
->where('client_contacts.deleted_at', '=', null)
|
||||
//->whereRaw('(clients.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
// DB::raw('COALESCE(clients.currency_id, companies.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(clients.country_id, companies.country_id) country_id'),
|
||||
DB::raw("CONCAT(COALESCE(client_contacts.first_name, ''), ' ', COALESCE(client_contacts.last_name, '')) contact"),
|
||||
'clients.id',
|
||||
|
@ -105,7 +105,6 @@ class DesignFilters extends QueryFilters
|
||||
$query = DB::table('designs')
|
||||
->join('companies', 'companies.id', '=', 'designs.company_id')
|
||||
->where('designs.company_id', '=', $company_id)
|
||||
//->whereRaw('(designs.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
'designs.id',
|
||||
'designs.name',
|
||||
|
@ -205,9 +205,7 @@ class ExpenseFilters extends QueryFilters
|
||||
$query = DB::table('expenses')
|
||||
->join('companies', 'companies.id', '=', 'expenses.company_id')
|
||||
->where('expenses.company_id', '=', $company_id)
|
||||
//->whereRaw('(expenses.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
// DB::raw('COALESCE(expenses.currency_id, companies.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(expenses.country_id, companies.country_id) country_id'),
|
||||
DB::raw("CONCAT(COALESCE(expense_contacts.first_name, ''), ' ', COALESCE(expense_contacts.last_name, '')) contact"),
|
||||
'expenses.id',
|
||||
|
@ -104,7 +104,6 @@ class TokenFilters extends QueryFilters
|
||||
$query = DB::table('company_tokens')
|
||||
->join('companies', 'companies.id', '=', 'company_tokens.company_id')
|
||||
->where('company_tokens.company_id', '=', $company_id)
|
||||
//->whereRaw('(designs.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
'company_tokens.id',
|
||||
'company_tokens.name',
|
||||
|
@ -118,7 +118,6 @@ class VendorFilters extends QueryFilters
|
||||
->where('vendors.company_id', '=', $company_id)
|
||||
->where('vendor_contacts.is_primary', '=', true)
|
||||
->where('vendor_contacts.deleted_at', '=', null)
|
||||
//->whereRaw('(vendors.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
// DB::raw('COALESCE(vendors.currency_id, companies.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(vendors.country_id, companies.country_id) country_id'),
|
||||
|
@ -105,7 +105,6 @@ class WebhookFilters extends QueryFilters
|
||||
$query = DB::table('webhooks')
|
||||
->join('companies', 'companies.id', '=', 'webhooks.company_id')
|
||||
->where('webhooks.company_id', '=', $company_id)
|
||||
//->whereRaw('(designs.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
'webhooks.id',
|
||||
'webhooks.target_url',
|
||||
|
@ -13,6 +13,7 @@
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\RecurringInvoices\ShowRecurringInvoiceRequest;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Http\Request;
|
||||
@ -38,4 +39,20 @@ class SubscriptionController extends Controller
|
||||
|
||||
return render('subscriptions.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the recurring invoice.
|
||||
*
|
||||
* @param ShowRecurringInvoiceRequest $request
|
||||
* @param RecurringInvoice $recurring_invoice
|
||||
* @return Factory|View
|
||||
*/
|
||||
public function show(ShowRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice)
|
||||
{
|
||||
return $this->render('subscriptions.show', [
|
||||
'invoice' => $recurring_invoice->load('invoices','subscription'),
|
||||
'subscription' => $recurring_invoice->subscription
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ class SubscriptionPlanSwitchController extends Controller
|
||||
*/
|
||||
public function index(ShowPlanSwitchRequest $request, RecurringInvoice $recurring_invoice, Subscription $target)
|
||||
{
|
||||
|
||||
$amount = $recurring_invoice->subscription
|
||||
->service()
|
||||
->calculateUpgradePriceV2($recurring_invoice, $target);
|
||||
@ -44,7 +45,6 @@ class SubscriptionPlanSwitchController extends Controller
|
||||
render('subscriptions.denied');
|
||||
}
|
||||
|
||||
|
||||
$amount = max(0,$amount);
|
||||
|
||||
return render('subscriptions.switch', [
|
||||
@ -53,5 +53,6 @@ class SubscriptionPlanSwitchController extends Controller
|
||||
'target' => $target,
|
||||
'amount' => $amount,
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,10 @@ class PaymentWebhookController extends Controller
|
||||
{
|
||||
public function __invoke(PaymentWebhookRequest $request)
|
||||
{
|
||||
//return early if we cannot resolve the company gateway
|
||||
if(!$request->getCompanyGateway())
|
||||
return response()->json([], 200);
|
||||
|
||||
return $request
|
||||
->getCompanyGateway()
|
||||
->driver()
|
||||
|
51
app/Http/Livewire/SubscriptionsTable.php
Normal file
51
app/Http/Livewire/SubscriptionsTable.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?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;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Traits\WithSorting;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class SubscriptionsTable extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
use WithSorting;
|
||||
|
||||
public $per_page = 10;
|
||||
|
||||
public $company;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = RecurringInvoice::query()
|
||||
->where('client_id', auth()->guard('contact')->user()->client->id)
|
||||
->where('company_id', $this->company->id)
|
||||
->whereNotNull('subscription_id')
|
||||
->where('is_deleted', false)
|
||||
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->withTrashed()
|
||||
->paginate($this->per_page);
|
||||
|
||||
return render('components.livewire.subscriptions-table', [
|
||||
'recurring_invoices' => $query,
|
||||
]);
|
||||
}
|
||||
}
|
@ -47,7 +47,8 @@ class PaymentWebhookRequest extends Request
|
||||
{
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
|
||||
return CompanyGateway::withTrashed()->findOrFail($this->decodePrimaryKey($this->company_gateway_id));
|
||||
return CompanyGateway::withTrashed()->find($this->decodePrimaryKey($this->company_gateway_id));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,35 +11,33 @@
|
||||
|
||||
namespace App\Mail\RecurringInvoice;
|
||||
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class ClientContactRequestCancellationObject
|
||||
{
|
||||
public $recurring_invoice;
|
||||
|
||||
public $client_contact;
|
||||
|
||||
private $company;
|
||||
|
||||
public function __construct($recurring_invoice, $client_contact)
|
||||
{
|
||||
$this->recurring_invoice = $recurring_invoice;
|
||||
$this->client_contact = $client_contact;
|
||||
$this->company = $recurring_invoice->company;
|
||||
}
|
||||
public function __construct(public RecurringInvoice $recurring_invoice, public ClientContact $client_contact, private bool $gateway_refund_attempted){}
|
||||
|
||||
public function build()
|
||||
{
|
||||
$this->company = $this->recurring_invoice->company;
|
||||
|
||||
App::forgetInstance('translator');
|
||||
App::setLocale($this->company->getLocale());
|
||||
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
$content = ctrans('texts.recurring_cancellation_request_body', ['contact' => $this->client_contact->present()->name(), 'client' => $this->client_contact->client->present()->name(), 'invoice' => $this->recurring_invoice->number]);
|
||||
|
||||
if($this->gateway_refund_attempted)
|
||||
$content .= "\n\n" . ctrans('texts.status') . " : " . ctrans('texts.payment_status_6');
|
||||
|
||||
$data = [
|
||||
'title' => ctrans('texts.recurring_cancellation_request', ['contact' => $this->client_contact->present()->name()]),
|
||||
'content' => ctrans('texts.recurring_cancellation_request_body', ['contact' => $this->client_contact->present()->name(), 'client' => $this->client_contact->client->present()->name(), 'invoice' => $this->recurring_invoice->number]),
|
||||
'content' => $content,
|
||||
'url' => config('ninja.web_url'),
|
||||
'button' => ctrans('texts.account_login'),
|
||||
'signature' => $this->company->settings->email_signature,
|
||||
|
79
app/Notifications/Ninja/RenewalFailureNotification.php
Normal file
79
app/Notifications/Ninja/RenewalFailureNotification.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?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\Notifications\Ninja;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class RenewalFailureNotification extends Notification
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
public function __construct(protected string $notification_message){}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['slack'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public function toSlack($notifiable)
|
||||
{
|
||||
$content = "Plan paid, account not updated\n";
|
||||
$content .= "Contact: {$this->notification_message}";
|
||||
|
||||
return (new SlackMessage)
|
||||
->success()
|
||||
->from(ctrans('texts.notification_bot'))
|
||||
->image('https://app.invoiceninja.com/favicon.png')
|
||||
->content($content);
|
||||
}
|
||||
}
|
@ -212,9 +212,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
}
|
||||
|
||||
|
||||
@ -250,9 +250,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
}
|
||||
|
||||
if (substr($cgt->token, 0, 2) === 'pm') {
|
||||
@ -494,9 +494,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
}
|
||||
|
||||
if (substr($source->token, 0, 2) === 'pm') {
|
||||
|
@ -63,9 +63,9 @@ class Charge
|
||||
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
}
|
||||
|
||||
$this->stripe->init();
|
||||
|
@ -61,9 +61,8 @@ class CreditCard
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
|
||||
// $description = $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')) . " for client {$this->stripe->client->present()->name()}";
|
||||
$invoice_numbers = collect($data['invoices'])->pluck('invoice_number')->implode(',');
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers, 'amount' => Number::formatMoney($data['total']['amount_with_fee'], $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers, 'amount' => Number::formatMoney($data['total']['amount_with_fee'], $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
|
||||
$payment_intent_data = [
|
||||
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
|
@ -53,9 +53,9 @@ class Klarna
|
||||
$invoice_numbers = collect($data['invoices'])->pluck('invoice_number');
|
||||
|
||||
if ($invoice_numbers->count() > 0) {
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers->implode(', '), 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers->implode(', '), 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
} else {
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()], $this->stripe->client->company->locale());
|
||||
}
|
||||
|
||||
$intent = \Stripe\PaymentIntent::create([
|
||||
|
@ -393,6 +393,8 @@ class SubscriptionService
|
||||
if(!$invoice)
|
||||
return [];
|
||||
|
||||
$handle_discount = false;
|
||||
|
||||
/* depending on whether we are creating an invoice or a credit*/
|
||||
$multiplier = $is_credit ? 1 : -1;
|
||||
|
||||
@ -408,17 +410,27 @@ class SubscriptionService
|
||||
|
||||
$line_items = [];
|
||||
|
||||
//Handle when we are refunding a discounted invoice. Need to consider the
|
||||
//total discount and also the line item discount.
|
||||
if($invoice->discount > 0)
|
||||
$handle_discount = true;
|
||||
|
||||
|
||||
foreach($invoice->line_items as $item)
|
||||
{
|
||||
|
||||
if($item->product_key != ctrans('texts.refund') && ($item->type_id == "1" || $item->type_id == "2"))
|
||||
{
|
||||
|
||||
$item->cost = ($item->cost*$ratio*$multiplier);
|
||||
$discount_ratio = 1;
|
||||
|
||||
if($handle_discount)
|
||||
$discount_ratio = $this->calculateDiscountRatio($invoice);
|
||||
|
||||
$item->cost = ($item->cost*$ratio*$multiplier*$discount_ratio);
|
||||
$item->product_key = ctrans('texts.refund');
|
||||
$item->notes = ctrans('texts.refund') . ": ". $item->notes;
|
||||
|
||||
|
||||
$line_items[] = $item;
|
||||
|
||||
}
|
||||
@ -428,6 +440,23 @@ class SubscriptionService
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We only charge for the used days
|
||||
*
|
||||
* @param Invoice $invoice
|
||||
* @return float
|
||||
*/
|
||||
public function calculateDiscountRatio($invoice) : float
|
||||
{
|
||||
|
||||
if($invoice->is_amount_discount)
|
||||
return $invoice->discount / ($invoice->amount + $invoice->discount);
|
||||
else
|
||||
return $invoice->discount / 100;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We only charge for the used days
|
||||
*
|
||||
@ -679,8 +708,9 @@ class SubscriptionService
|
||||
}
|
||||
else if($last_invoice->balance > 0)
|
||||
{
|
||||
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||
$last_invoice = null;
|
||||
// $pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||
// nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1178,11 +1208,15 @@ class SubscriptionService
|
||||
{
|
||||
$invoice_start_date = false;
|
||||
$refund_end_date = false;
|
||||
$gateway_refund_attempted = false;
|
||||
|
||||
//only refund if they are in the refund window.
|
||||
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id)
|
||||
->where('client_id', $recurring_invoice->client_id)
|
||||
->where('status_id', Invoice::STATUS_PAID)
|
||||
->where('is_deleted', 0)
|
||||
->where('is_proforma',0)
|
||||
->where('balance',0)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
@ -1198,7 +1232,7 @@ class SubscriptionService
|
||||
$recurring_invoice_repo->archive($recurring_invoice);
|
||||
|
||||
/* Refund only if we are in the window - and there is nothing outstanding on the invoice */
|
||||
if($refund_end_date && $refund_end_date->greaterThan(now()) && (int)$outstanding_invoice->balance == 0)
|
||||
if($refund_end_date && $refund_end_date->greaterThan(now()))
|
||||
{
|
||||
|
||||
if($outstanding_invoice->payments()->exists())
|
||||
@ -1207,8 +1241,9 @@ class SubscriptionService
|
||||
|
||||
$data = [
|
||||
'id' => $payment->id,
|
||||
'gateway_refund' => true,
|
||||
'gateway_refund' => $outstanding_invoice->amount >= 1 ? true : false,
|
||||
'send_email' => true,
|
||||
'email_receipt',
|
||||
'invoices' => [
|
||||
['invoice_id' => $outstanding_invoice->id, 'amount' => $outstanding_invoice->amount],
|
||||
],
|
||||
@ -1216,6 +1251,7 @@ class SubscriptionService
|
||||
];
|
||||
|
||||
$payment->refund($data);
|
||||
$gateway_refund_attempted = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1231,7 +1267,7 @@ class SubscriptionService
|
||||
$this->triggerWebhook($context);
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->guard('contact')->user()))->build()));
|
||||
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->guard('contact')->user(), $gateway_refund_attempted))->build()));
|
||||
$nmo->company = $recurring_invoice->company;
|
||||
$nmo->settings = $recurring_invoice->company->settings;
|
||||
|
||||
|
@ -10,11 +10,21 @@
|
||||
<script src="{{ asset('js/pdf.min.js') }}"></script>
|
||||
@if(\App\Utils\Ninja::isHosted())
|
||||
<script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
|
||||
|
||||
<script type="text/javascript"
|
||||
src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js"
|
||||
integrity="sha384-ggh+EF1aSqm+Y4yvv2n17KpurNcZTeYtUZUvhPziElsstmIEubyEB6AIVpKLuZgr"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<!-- Google Tag Manager -->
|
||||
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-WMJ5W23');</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
|
||||
@endif
|
||||
<script type="text/javascript">
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "{{ asset('js/pdf.worker.min.js') }}";
|
||||
@ -29,6 +39,13 @@
|
||||
</head>
|
||||
<body style="background-color:#888888;">
|
||||
|
||||
@if(\App\Utils\Ninja::isHosted())
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WMJ5W23"
|
||||
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
@endif
|
||||
|
||||
<style>
|
||||
|
||||
/* fix for blurry fonts
|
||||
|
@ -1,10 +1,5 @@
|
||||
<div>
|
||||
<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"
|
||||
translate="yes">
|
||||
One-time payments
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-between mt-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||
@ -21,111 +16,58 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||
{{ ctrans('texts.invoice') }}
|
||||
</span>
|
||||
<p role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||
{{ ctrans('texts.subscription') }}
|
||||
</p>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<span role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||
{{ ctrans('texts.total') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<span role="button" wire:click="sortBy('public_notes')" class="cursor-pointer">
|
||||
{{ ctrans('texts.date') }}
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($invoices as $invoice)
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||
class="button-link text-primary">
|
||||
{{ $invoice->number }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ $invoice->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()) }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||
{{ ctrans('texts.no_results') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||
@if($invoices->total() > 0)
|
||||
<span class="text-gray-700 text-sm hidden md:block">
|
||||
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||
</span>
|
||||
@endif
|
||||
{{ $invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||
</div>
|
||||
|
||||
<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 mt-4"
|
||||
translate="yes">
|
||||
Subscriptions
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-between mt-4">
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||
<option>5</option>
|
||||
<option selected>10</option>
|
||||
<option>15</option>
|
||||
<option>20</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||
{{ ctrans('texts.invoice') }}
|
||||
</span>
|
||||
<p role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||
{{ ctrans('texts.frequency') }}
|
||||
</p>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<span role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||
{{ ctrans('texts.total') }}
|
||||
</span>
|
||||
<p role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||
{{ ctrans('texts.amount') }}
|
||||
</p>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<p role="button" wire:click="sortBy('auto_bill_enabled')" class="cursor-pointer">
|
||||
{{ ctrans('texts.auto_bill') }}
|
||||
</p>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<p role="button" wire:click="sortBy('next_send_date')" class="cursor-pointer">
|
||||
{{ ctrans('texts.next_send_date') }}
|
||||
</p>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<span role="button" wire:click="sortBy('public_notes')" class="cursor-pointer">
|
||||
{{ ctrans('texts.date') }}
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($invoices as $invoice)
|
||||
@forelse($recurring_invoices as $recurring_invoice)
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||
{{ $recurring_invoice->subscription->name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ \App\Models\RecurringInvoice::frequencyForKey($recurring_invoice->frequency_id) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ App\Utils\Number::formatMoney($recurring_invoice->amount, $recurring_invoice->client) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ $recurring_invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ $recurring_invoice->translateDate($recurring_invoice->date, $recurring_invoice->client->date_format(), $recurring_invoice->client->locale()) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
<a href="{{ route('client.subscriptions.show', $recurring_invoice->hashed_id) }}"
|
||||
class="button-link text-primary">
|
||||
{{ $invoice->number }}
|
||||
{{ ctrans('texts.view') }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ $invoice->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()) }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
@ -138,13 +80,13 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||
@if($invoices->total() > 0)
|
||||
@if($recurring_invoices->total() > 0)
|
||||
<span class="text-gray-700 text-sm hidden md:block">
|
||||
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||
{{ ctrans('texts.showing_x_of', ['first' => $recurring_invoices->firstItem(), 'last' => $recurring_invoices->lastItem(), 'total' => $recurring_invoices->total()]) }}
|
||||
</span>
|
||||
@endif
|
||||
{{ $invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||
{{ $recurring_invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -3,6 +3,6 @@
|
||||
|
||||
@section('body')
|
||||
<div class="flex flex-col">
|
||||
@livewire('subscription-recurring-invoices-table', ['company' => $company])
|
||||
@livewire('subscriptions-table', ['company' => $company])
|
||||
</div>
|
||||
@endsection
|
||||
|
108
resources/views/portal/ninja2020/subscriptions/show.blade.php
Normal file
108
resources/views/portal/ninja2020/subscriptions/show.blade.php
Normal file
@ -0,0 +1,108 @@
|
||||
@extends('portal.ninja2020.layout.app')
|
||||
@section('meta_title', ctrans('texts.subscription'))
|
||||
|
||||
@section('body')
|
||||
<div class="container mx-auto">
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ $subscription->name }}.
|
||||
</h3>
|
||||
</div>
|
||||
<div>
|
||||
<dl>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.start_date') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $invoice->translateDate($invoice->start_date, $invoice->client->date_format(), $invoice->client->locale()) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.next_send_date') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $invoice->translateDate(\Carbon\Carbon::parse($invoice->next_send_date)->subSeconds($invoice->client->timezone_offset()), $invoice->client->date_format(), $invoice->client->locale()) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.frequency') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ \App\Models\RecurringInvoice::frequencyForKey($invoice->frequency_id) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.cycles_remaining') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $invoice->remaining_cycles == '-1' ? ctrans('texts.endless') : $invoice->remaining_cycles }}
|
||||
@if($invoice->remaining_cycles == '-1') ∞ @endif
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.amount') }}
|
||||
</dt>
|
||||
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ \App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||
</div>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@include('portal.ninja2020.components.entity-documents', ['entity' => $invoice])
|
||||
|
||||
@if($invoice->auto_bill === 'optin' || $invoice->auto_bill === 'optout')
|
||||
<div class="bg-white shadow overflow-hidden lg:rounded-lg mt-4">
|
||||
<div class="flex flex-col md:flex-row items-start justify-between px-4 py-5 sm:p-6">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">{{ ctrans('texts.auto_bill') }}</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">{{ ctrans('texts.auto_bill_option')}}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-4 space-x-2">
|
||||
@livewire('recurring-invoices.update-auto-billing', ['invoice' => $invoice])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($invoice->subscription && $invoice->subscription?->allow_cancellation)
|
||||
<div class="bg-white shadow sm:rounded-lg mt-4">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.cancellation') }}
|
||||
</h3>
|
||||
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500" x-data="{ open: false }">
|
||||
<button class="button button-danger" translate @click="open = true">{{ ctrans('texts.request_cancellation') }}
|
||||
</button>
|
||||
@include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($invoice->subscription && $invoice->subscription->allow_plan_changes)
|
||||
<div class="bg-white shadow overflow-hidden px-4 py-5 lg:rounded-lg mt-4">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">{{ ctrans('texts.change_plan') }}</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">Upgrade or downgrade your current plan.</p>
|
||||
|
||||
<div class="flex mt-4 space-x-2">
|
||||
@foreach($invoice->subscription->service()->getPlans() as $subscription)
|
||||
<a href="{{ route('client.subscription.plan_switch', ['recurring_invoice' => $invoice->hashed_id, 'target' => $subscription->hashed_id]) }}" class="border rounded px-5 py-2 hover:border-gray-800 text-sm cursor-pointer">{{ $subscription->name }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
@ -98,8 +98,9 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db','check_clie
|
||||
Route::resource('documents', App\Http\Controllers\ClientPortal\DocumentController::class)->only(['index', 'show']);
|
||||
|
||||
Route::get('subscriptions/{recurring_invoice}/plan_switch/{target}', [App\Http\Controllers\ClientPortal\SubscriptionPlanSwitchController::class, 'index'])->name('subscription.plan_switch');
|
||||
|
||||
Route::resource('subscriptions', SubscriptionController::class)->middleware('portal_enabled')->only(['index']);
|
||||
|
||||
Route::get('subscriptions/{recurring_invoice}', [SubscriptionController::class, 'show'])->middleware('portal_enabled')->name('subscriptions.show');
|
||||
Route::get('subscriptions', [SubscriptionController::class, 'index'])->middleware('portal_enabled')->name('subscriptions.index');
|
||||
|
||||
Route::resource('tasks', TaskController::class)->only(['index']);
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Helpers\Invoice\ProRata;
|
||||
use App\Helpers\Subscription\SubscriptionCalculator;
|
||||
use App\Models\Invoice;
|
||||
@ -101,4 +102,121 @@ class SubscriptionsCalcTest extends TestCase
|
||||
|
||||
// $this->assertEquals(3.23, $upgrade);
|
||||
}
|
||||
|
||||
public function testProrataDiscountRatioPercentage()
|
||||
{
|
||||
|
||||
$subscription = Subscription::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'price' => 100,
|
||||
]);
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 1;
|
||||
|
||||
$item->cost = 100;
|
||||
$item->product_key = 'xyz';
|
||||
$item->notes = 'test';
|
||||
$item->custom_value1 = 'x';
|
||||
$item->custom_value2 = 'x';
|
||||
$item->custom_value3 = 'x';
|
||||
$item->custom_value4 = 'x';
|
||||
|
||||
$line_items[] = $item;
|
||||
|
||||
$invoice = Invoice::factory()->create([
|
||||
'line_items' => $line_items,
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'tax_rate1' => 0,
|
||||
'tax_name1' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate3' => 0,
|
||||
'tax_name3' => '',
|
||||
'discount' => 0,
|
||||
'subscription_id' => $subscription->id,
|
||||
'date' => '2021-01-01',
|
||||
'discount' => 10,
|
||||
'is_amount_discount' => false,
|
||||
'status_id' => 1,
|
||||
]);
|
||||
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$this->assertEquals(90, $invoice->amount);
|
||||
$this->assertEquals(0, $invoice->balance);
|
||||
|
||||
$invoice->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(90, $invoice->amount);
|
||||
$this->assertEquals(90, $invoice->balance);
|
||||
|
||||
|
||||
$ratio = $subscription->service()->calculateDiscountRatio($invoice);
|
||||
|
||||
$this->assertEquals(.1, $ratio);
|
||||
|
||||
}
|
||||
|
||||
public function testProrataDiscountRatioAmount()
|
||||
{
|
||||
|
||||
$subscription = Subscription::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'price' => 100,
|
||||
]);
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 1;
|
||||
|
||||
$item->cost = 100;
|
||||
$item->product_key = 'xyz';
|
||||
$item->notes = 'test';
|
||||
$item->custom_value1 = 'x';
|
||||
$item->custom_value2 = 'x';
|
||||
$item->custom_value3 = 'x';
|
||||
$item->custom_value4 = 'x';
|
||||
|
||||
$line_items[] = $item;
|
||||
|
||||
$invoice = Invoice::factory()->create([
|
||||
'line_items' => $line_items,
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'tax_rate1' => 0,
|
||||
'tax_name1' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate3' => 0,
|
||||
'tax_name3' => '',
|
||||
'discount' => 0,
|
||||
'subscription_id' => $subscription->id,
|
||||
'date' => '2021-01-01',
|
||||
'discount' => 20,
|
||||
'is_amount_discount' => true,
|
||||
'status_id' => 1,
|
||||
]);
|
||||
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
$this->assertEquals(80, $invoice->amount);
|
||||
$this->assertEquals(0, $invoice->balance);
|
||||
|
||||
$invoice->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(80, $invoice->amount);
|
||||
$this->assertEquals(80, $invoice->balance);
|
||||
|
||||
|
||||
$ratio = $subscription->service()->calculateDiscountRatio($invoice);
|
||||
|
||||
$this->assertEquals(.2, $ratio);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user