mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
660dd976de
@ -23,6 +23,7 @@ use App\Models\Invoice;
|
|||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use App\Models\PaymentHash;
|
use App\Models\PaymentHash;
|
||||||
use App\Models\SystemLog;
|
use App\Models\SystemLog;
|
||||||
|
use App\Services\Subscription\SubscriptionService;
|
||||||
use App\Utils\Number;
|
use App\Utils\Number;
|
||||||
use App\Utils\Traits\MakesDates;
|
use App\Utils\Traits\MakesDates;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
@ -342,6 +343,12 @@ class PaymentController extends Controller
|
|||||||
|
|
||||||
$payment = $payment->service()->applyCredits($payment_hash)->save();
|
$payment = $payment->service()->applyCredits($payment_hash)->save();
|
||||||
|
|
||||||
|
if (property_exists($payment_hash->data, 'billing_context')) {
|
||||||
|
$billing_subscription = \App\Models\Subscription::find($payment_hash->data->billing_context->subscription_id);
|
||||||
|
|
||||||
|
return (new SubscriptionService($billing_subscription))->completePurchase($payment_hash);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,6 +674,8 @@ class Import implements ShouldQueue
|
|||||||
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
|
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
|
||||||
$resource['invitations'][$key]['company_id'] = $this->company->id;
|
$resource['invitations'][$key]['company_id'] = $this->company->id;
|
||||||
unset($resource['invitations'][$key]['recurring_invoice_id']);
|
unset($resource['invitations'][$key]['recurring_invoice_id']);
|
||||||
|
unset($resource['invitations'][$key]['id']);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
|
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
|
||||||
@ -736,6 +738,7 @@ class Import implements ShouldQueue
|
|||||||
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
|
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
|
||||||
$resource['invitations'][$key]['company_id'] = $this->company->id;
|
$resource['invitations'][$key]['company_id'] = $this->company->id;
|
||||||
unset($resource['invitations'][$key]['invoice_id']);
|
unset($resource['invitations'][$key]['invoice_id']);
|
||||||
|
unset($resource['invitations'][$key]['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
|
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
|
||||||
@ -864,6 +867,7 @@ class Import implements ShouldQueue
|
|||||||
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
|
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
|
||||||
$resource['invitations'][$key]['company_id'] = $this->company->id;
|
$resource['invitations'][$key]['company_id'] = $this->company->id;
|
||||||
unset($resource['invitations'][$key]['invoice_id']);
|
unset($resource['invitations'][$key]['invoice_id']);
|
||||||
|
unset($resource['invitations'][$key]['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
|
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
|
||||||
|
@ -148,7 +148,7 @@ class ApplyPayment
|
|||||||
|
|
||||||
if ((int)$this->invoice->balance == 0) {
|
if ((int)$this->invoice->balance == 0) {
|
||||||
$this->invoice->service()->deletePdf();
|
$this->invoice->service()->deletePdf();
|
||||||
event(new InvoiceWasPaid($this->invoice, $payment, $this->payment->company, Ninja::eventVars(auth()->user()->id)));
|
event(new InvoiceWasPaid($this->invoice, $this->payment, $this->payment->company, Ninja::eventVars(auth()->user()->id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ class SubscriptionService
|
|||||||
|
|
||||||
if($payment_hash->data->billing_context->context == 'change_plan') {
|
if($payment_hash->data->billing_context->context == 'change_plan') {
|
||||||
return $this->handlePlanChange($payment_hash);
|
return $this->handlePlanChange($payment_hash);
|
||||||
};
|
}
|
||||||
|
|
||||||
// if we have a recurring product - then generate a recurring invoice
|
// if we have a recurring product - then generate a recurring invoice
|
||||||
if(strlen($this->subscription->recurring_product_ids) >=1){
|
if(strlen($this->subscription->recurring_product_ids) >=1){
|
||||||
@ -132,7 +132,7 @@ class SubscriptionService
|
|||||||
];
|
];
|
||||||
|
|
||||||
$response = $this->triggerWebhook($context);
|
$response = $this->triggerWebhook($context);
|
||||||
// nlog($response);
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +182,17 @@ class SubscriptionService
|
|||||||
return $this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
return $this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an upgrade price when moving between plans
|
||||||
|
*
|
||||||
|
* However we only allow people to move between plans
|
||||||
|
* if their account is in good standing.
|
||||||
|
*
|
||||||
|
* @param RecurringInvoice $recurring_invoice
|
||||||
|
* @param Subscription $target
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
public function calculateUpgradePrice(RecurringInvoice $recurring_invoice, Subscription $target) :?float
|
public function calculateUpgradePrice(RecurringInvoice $recurring_invoice, Subscription $target) :?float
|
||||||
{
|
{
|
||||||
//calculate based on daily prices
|
//calculate based on daily prices
|
||||||
@ -195,7 +206,7 @@ class SubscriptionService
|
|||||||
->where('balance', '>', 0);
|
->where('balance', '>', 0);
|
||||||
|
|
||||||
$outstanding_amounts = $outstanding->sum('balance');
|
$outstanding_amounts = $outstanding->sum('balance');
|
||||||
// $outstanding_invoices = $outstanding->get();
|
|
||||||
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id)
|
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id)
|
||||||
->where('client_id', $recurring_invoice->client_id)
|
->where('client_id', $recurring_invoice->client_id)
|
||||||
->where('is_deleted', 0)
|
->where('is_deleted', 0)
|
||||||
@ -247,6 +258,7 @@ class SubscriptionService
|
|||||||
* Returns refundable set of line items
|
* Returns refundable set of line items
|
||||||
* transformed for direct injection into
|
* transformed for direct injection into
|
||||||
* the invoice
|
* the invoice
|
||||||
|
*
|
||||||
* @param Invoice $invoice
|
* @param Invoice $invoice
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@ -287,7 +299,6 @@ class SubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We only charge for the used days
|
* We only charge for the used days
|
||||||
*
|
*
|
||||||
@ -314,6 +325,12 @@ class SubscriptionService
|
|||||||
return $pro_rata_charge;
|
return $pro_rata_charge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When downgrading, we may need to create
|
||||||
|
* a credit
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
public function createChangePlanCredit($data)
|
public function createChangePlanCredit($data)
|
||||||
{
|
{
|
||||||
$recurring_invoice = $data['recurring_invoice'];
|
$recurring_invoice = $data['recurring_invoice'];
|
||||||
@ -345,7 +362,7 @@ class SubscriptionService
|
|||||||
|
|
||||||
nlog("total payable = {$total_payable}");
|
nlog("total payable = {$total_payable}");
|
||||||
|
|
||||||
$credit = $this->createCredit($pro_rata_refund_amount, $last_invoice, $target_subscription, $old_subscription);
|
$credit = $this->createCredit($last_invoice, $target_subscription);
|
||||||
|
|
||||||
$new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice);
|
$new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice);
|
||||||
|
|
||||||
@ -366,6 +383,12 @@ class SubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When changing plans, we need to generate a pro rata invoice
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return Invoice
|
||||||
|
*/
|
||||||
public function createChangePlanInvoice($data)
|
public function createChangePlanInvoice($data)
|
||||||
{
|
{
|
||||||
$recurring_invoice = $data['recurring_invoice'];
|
$recurring_invoice = $data['recurring_invoice'];
|
||||||
@ -395,20 +418,19 @@ class SubscriptionService
|
|||||||
|
|
||||||
$total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price;
|
$total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price;
|
||||||
|
|
||||||
nlog("total payable = {$total_payable}");
|
return $this->proRataInvoice($last_invoice, $target_subscription);
|
||||||
|
|
||||||
|
|
||||||
return $this->proRataInvoice($pro_rata_refund_amount, $last_invoice, $target_subscription, $old_subscription);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response from payment service on return from a plan change
|
* Response from payment service on
|
||||||
|
* return from a plan change
|
||||||
*
|
*
|
||||||
|
* @param PaymentHash $payment_hash
|
||||||
*/
|
*/
|
||||||
private function handlePlanChange($payment_hash)
|
private function handlePlanChange($payment_hash)
|
||||||
{
|
{
|
||||||
|
|
||||||
$old_recurring_invoice = RecurringInvoice::find($payment_hash->data->billing_context->recurring_invoice);
|
$old_recurring_invoice = RecurringInvoice::find($payment_hash->data->billing_context->recurring_invoice);
|
||||||
|
|
||||||
$recurring_invoice = $this->createNewRecurringInvoice($old_recurring_invoice);
|
$recurring_invoice = $this->createNewRecurringInvoice($old_recurring_invoice);
|
||||||
@ -422,6 +444,7 @@ class SubscriptionService
|
|||||||
'contact' => auth('contact')->user()->hashed_id,
|
'contact' => auth('contact')->user()->hashed_id,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
$response = $this->triggerWebhook($context);
|
$response = $this->triggerWebhook($context);
|
||||||
|
|
||||||
nlog($response);
|
nlog($response);
|
||||||
@ -430,13 +453,20 @@ class SubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new recurring invoice when changing
|
||||||
|
* plans
|
||||||
|
*
|
||||||
|
* @param RecurringInvoice $old_recurring_invoice
|
||||||
|
* @return RecurringInvoice
|
||||||
|
*/
|
||||||
private function createNewRecurringInvoice($old_recurring_invoice) :RecurringInvoice
|
private function createNewRecurringInvoice($old_recurring_invoice) :RecurringInvoice
|
||||||
{
|
{
|
||||||
|
|
||||||
$old_recurring_invoice->service()->stop()->save();
|
$old_recurring_invoice->service()->stop()->save();
|
||||||
|
|
||||||
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||||
$recurring_invoice_repo->archive($$old_recurring_invoice);
|
$recurring_invoice_repo->archive($old_recurring_invoice);
|
||||||
|
|
||||||
$recurring_invoice = $this->convertInvoiceToRecurring($old_recurring_invoice->client_id);
|
$recurring_invoice = $this->convertInvoiceToRecurring($old_recurring_invoice->client_id);
|
||||||
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
||||||
@ -452,15 +482,14 @@ class SubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a plan change where no payment is required
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
public function handlePlanChangeNoPayment($data)
|
public function handlePlanChangeNoPayment($data)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
'recurring_invoice' => $this->recurring_invoice,
|
|
||||||
'subscription' => $this->subscription,
|
|
||||||
'target' => $this->target,
|
|
||||||
'hash' => $this->hash,
|
|
||||||
*/
|
|
||||||
|
|
||||||
$recurring_invoice = $this->createNewRecurringInvoice($data['recurring_invoice']);
|
$recurring_invoice = $this->createNewRecurringInvoice($data['recurring_invoice']);
|
||||||
|
|
||||||
$context = [
|
$context = [
|
||||||
@ -474,12 +503,19 @@ class SubscriptionService
|
|||||||
|
|
||||||
$response = $this->triggerWebhook($context);
|
$response = $this->triggerWebhook($context);
|
||||||
|
|
||||||
nlog($response);
|
// nlog($response);
|
||||||
|
|
||||||
return $this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
return $this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createCredit($refund_amount, $last_invoice, $target, $old_subscription)
|
/**
|
||||||
|
* Creates a credit note if the plan change requires
|
||||||
|
*
|
||||||
|
* @param Invoice $last_invoice
|
||||||
|
* @param Subscription $target
|
||||||
|
* @return Credit
|
||||||
|
*/
|
||||||
|
private function createCredit($last_invoice, $target)
|
||||||
{
|
{
|
||||||
|
|
||||||
$subscription_repo = new SubscriptionRepository();
|
$subscription_repo = new SubscriptionRepository();
|
||||||
@ -502,15 +538,16 @@ class SubscriptionService
|
|||||||
return $credit_repo->save($data, $credit)->service()->markSent()->fillDefaults()->save();
|
return $credit_repo->save($data, $credit)->service()->markSent()->fillDefaults()->save();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 'client_id' => 2,
|
* When changing plans we need to generate a pro rata
|
||||||
'date' => '2021-04-13',
|
* invoice which takes into account any credits.
|
||||||
'invitations' =>
|
*
|
||||||
'user_input_promo_code' => NULL,
|
* @param Invoice $last_invoice
|
||||||
'coupon' => '',
|
* @param Subscription $target
|
||||||
'quantity' => 1,
|
* @return Invoice
|
||||||
*/
|
*/
|
||||||
private function proRataInvoice($refund_amount, $last_invoice, $target, $old_subscription)
|
private function proRataInvoice($last_invoice, $target)
|
||||||
{
|
{
|
||||||
$subscription_repo = new SubscriptionRepository();
|
$subscription_repo = new SubscriptionRepository();
|
||||||
$invoice_repo = new InvoiceRepository();
|
$invoice_repo = new InvoiceRepository();
|
||||||
@ -519,9 +556,7 @@ class SubscriptionService
|
|||||||
$invoice->date = now()->format('Y-m-d');
|
$invoice->date = now()->format('Y-m-d');
|
||||||
$invoice->subscription_id = $this->subscription->id;
|
$invoice->subscription_id = $this->subscription->id;
|
||||||
|
|
||||||
$line_items = $subscription_repo->generateLineItems($target);
|
$invoice->line_items = array_merge($subscription_repo->generateLineItems($target), $this->calculateProRataRefundItems($last_invoice));
|
||||||
|
|
||||||
$invoice->line_items = array_merge($line_items, $this->calculateProRataRefundItems($last_invoice));
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'client_id' => $last_invoice->client_id,
|
'client_id' => $last_invoice->client_id,
|
||||||
@ -529,11 +564,20 @@ class SubscriptionService
|
|||||||
'date' => now()->format('Y-m-d'),
|
'date' => now()->format('Y-m-d'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $invoice_repo->save($data, $invoice)->service()->markSent()->fillDefaults()->save();
|
return $invoice_repo->save($data, $invoice)
|
||||||
|
->service()
|
||||||
|
->markSent()
|
||||||
|
->fillDefaults()
|
||||||
|
->save();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the first invoice when a subscription is purchased
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return Invoice
|
||||||
|
*/
|
||||||
public function createInvoice($data): ?\App\Models\Invoice
|
public function createInvoice($data): ?\App\Models\Invoice
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -554,7 +598,13 @@ class SubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a recurring invoice based on
|
||||||
|
* the specifications of the subscription
|
||||||
|
*
|
||||||
|
* @param int $client_id The Client Id
|
||||||
|
* @return RecurringInvoice
|
||||||
|
*/
|
||||||
public function convertInvoiceToRecurring($client_id) :RecurringInvoice
|
public function convertInvoiceToRecurring($client_id) :RecurringInvoice
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -571,6 +621,11 @@ class SubscriptionService
|
|||||||
return $recurring_invoice;
|
return $recurring_invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit a 3rd party API if defined in the subscription
|
||||||
|
*
|
||||||
|
* @param array $context
|
||||||
|
*/
|
||||||
public function triggerWebhook($context)
|
public function triggerWebhook($context)
|
||||||
{
|
{
|
||||||
/* If no webhooks have been set, then just return gracefully */
|
/* If no webhooks have been set, then just return gracefully */
|
||||||
@ -658,11 +713,14 @@ class SubscriptionService
|
|||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the cancellation of a subscription
|
||||||
|
*
|
||||||
|
* @param RecurringInvoice $recurring_invoice
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function handleCancellation(RecurringInvoice $recurring_invoice)
|
public function handleCancellation(RecurringInvoice $recurring_invoice)
|
||||||
{
|
{
|
||||||
//only allow cancellation of services that are paid up to date.
|
|
||||||
|
|
||||||
// $last_invoice =
|
|
||||||
|
|
||||||
//only refund if they are in the refund window.
|
//only refund if they are in the refund window.
|
||||||
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id)
|
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id)
|
||||||
@ -679,11 +737,10 @@ class SubscriptionService
|
|||||||
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||||
$recurring_invoice_repo->archive($recurring_invoice);
|
$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->greaterThan(now()) && (int)$outstanding_invoice->balance == 0)
|
if($refund_end_date->greaterThan(now()) && (int)$outstanding_invoice->balance == 0)
|
||||||
{
|
{
|
||||||
//we are in the refund window.
|
|
||||||
//
|
|
||||||
//$outstanding_invoice
|
|
||||||
if($outstanding_invoice->payments()->exists())
|
if($outstanding_invoice->payments()->exists())
|
||||||
{
|
{
|
||||||
$payment = $outstanding_invoice->payments()->first();
|
$payment = $outstanding_invoice->payments()->first();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="col-span-12 md:col-span-5 md:col-start-4 px-4 py-5">
|
<div class="col-span-12 md:col-span-5 md:col-start-4 px-4 py-5">
|
||||||
<!-- Total price -->
|
<!-- Total price -->
|
||||||
|
|
||||||
@if(isset($state['invoice']))
|
@if($amount > 0)
|
||||||
|
|
||||||
<div class="relative mt-8">
|
<div class="relative mt-8">
|
||||||
<div class="absolute inset-0 flex items-center">
|
<div class="absolute inset-0 flex items-center">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user