mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 05:34:30 -04:00
Build pipeline for payment
This commit is contained in:
parent
112efdaadb
commit
c010c99547
@ -1,96 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Invoice Ninja (https://invoiceninja.com).
|
|
||||||
*
|
|
||||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
|
||||||
*
|
|
||||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
|
||||||
*
|
|
||||||
* @license https://www.elastic.co/licensing/elastic-license
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Helpers\Subscription;
|
|
||||||
|
|
||||||
use App\Helpers\Invoice\ProRata;
|
|
||||||
use App\Models\Invoice;
|
|
||||||
use App\Models\Subscription;
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SubscriptionCalculator.
|
|
||||||
*/
|
|
||||||
class SubscriptionCalculator
|
|
||||||
{
|
|
||||||
public Subscription $target_subscription;
|
|
||||||
|
|
||||||
public Invoice $invoice;
|
|
||||||
|
|
||||||
public function __construct(Subscription $target_subscription, Invoice $invoice)
|
|
||||||
{
|
|
||||||
$this->target_subscription = $target_subscription;
|
|
||||||
$this->invoice = $invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if the user is currently up
|
|
||||||
* to date with their payments for
|
|
||||||
* a given recurring invoice
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isPaidUp(): bool
|
|
||||||
{
|
|
||||||
$outstanding_invoices_exist = Invoice::query()->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
|
||||||
->where('subscription_id', $this->invoice->subscription_id)
|
|
||||||
->where('client_id', $this->invoice->client_id)
|
|
||||||
->where('balance', '>', 0)
|
|
||||||
->exists();
|
|
||||||
|
|
||||||
return ! $outstanding_invoices_exist;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function calcUpgradePlan()
|
|
||||||
{
|
|
||||||
//set the starting refund amount
|
|
||||||
$refund_amount = 0;
|
|
||||||
|
|
||||||
$refund_invoice = false;
|
|
||||||
|
|
||||||
//are they paid up to date.
|
|
||||||
|
|
||||||
//yes - calculate refund
|
|
||||||
if ($this->isPaidUp()) {
|
|
||||||
$refund_invoice = $this->getRefundInvoice();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($refund_invoice) {
|
|
||||||
/** @var \App\Models\Subscription $subscription **/
|
|
||||||
$subscription = Subscription::find($this->invoice->subscription_id);
|
|
||||||
$pro_rata = new ProRata();
|
|
||||||
|
|
||||||
$to_date = $subscription->service()->getNextDateForFrequency(Carbon::parse($refund_invoice->date), $subscription->frequency_id);
|
|
||||||
|
|
||||||
$refund_amount = $pro_rata->refund($refund_invoice->amount, now(), $to_date, $subscription->frequency_id);
|
|
||||||
|
|
||||||
$charge_amount = $pro_rata->charge($this->target_subscription->price, now(), $to_date, $this->target_subscription->frequency_id);
|
|
||||||
|
|
||||||
return $charge_amount - $refund_amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
//no - return full freight charge.
|
|
||||||
return $this->target_subscription->price;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function executeUpgradePlan()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getRefundInvoice()
|
|
||||||
{
|
|
||||||
return Invoice::where('subscription_id', $this->invoice->subscription_id)
|
|
||||||
->where('client_id', $this->invoice->client_id)
|
|
||||||
->where('is_deleted', 0)
|
|
||||||
->orderBy('id', 'desc')
|
|
||||||
->first();
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,8 +12,9 @@
|
|||||||
|
|
||||||
namespace App\Livewire\BillingPortal\Payments;
|
namespace App\Livewire\BillingPortal\Payments;
|
||||||
|
|
||||||
use App\Models\Subscription;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class Methods extends Component
|
class Methods extends Component
|
||||||
{
|
{
|
||||||
@ -36,7 +37,32 @@ class Methods extends Component
|
|||||||
|
|
||||||
public function handleSelect(string $company_gateway_id, string $gateway_type_id)
|
public function handleSelect(string $company_gateway_id, string $gateway_type_id)
|
||||||
{
|
{
|
||||||
dd($this->context['bundle']);
|
|
||||||
|
$this->dispatch('purchase.context', property: 'client_id', value: auth()->guard('contact')->user()->client->hashed_id);
|
||||||
|
|
||||||
|
nlog($this->context);
|
||||||
|
|
||||||
|
$invoice = $this->subscription
|
||||||
|
->calc()
|
||||||
|
->buildPurchaseInvoice($this->context)
|
||||||
|
->service()
|
||||||
|
->fillDefaults()
|
||||||
|
->adjustInventory()
|
||||||
|
->save();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Cache::put($this->hash, [
|
||||||
|
'subscription_id' => $this->subscription->hashed_id,
|
||||||
|
'email' => auth()->guard('contact')->user()->email,
|
||||||
|
'client_id' => auth()->guard('contact')->user()->client->hashed_id,
|
||||||
|
'invoice_id' => $invoice->hashed_id,
|
||||||
|
'context' => 'purchase',
|
||||||
|
'campaign' => $this->context['campaign'],
|
||||||
|
'bundle' => $this->context['bundle'],
|
||||||
|
], now()->addMinutes(60));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Services\Subscription\PaymentLinkService;
|
use App\Services\Subscription\PaymentLinkService;
|
||||||
|
use App\Services\Subscription\SubscriptionCalculator;
|
||||||
use App\Services\Subscription\SubscriptionService;
|
use App\Services\Subscription\SubscriptionService;
|
||||||
use App\Services\Subscription\SubscriptionStatus;
|
use App\Services\Subscription\SubscriptionStatus;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
@ -146,6 +147,11 @@ class Subscription extends BaseModel
|
|||||||
return (new SubscriptionStatus($this, $recurring_invoice))->run();
|
return (new SubscriptionStatus($this, $recurring_invoice))->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function calc(): SubscriptionCalculator
|
||||||
|
{
|
||||||
|
return new SubscriptionCalculator($this);
|
||||||
|
}
|
||||||
|
|
||||||
public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Company::class);
|
return $this->belongsTo(Company::class);
|
||||||
|
182
app/Services/Subscription/SubscriptionCalculator.php
Normal file
182
app/Services/Subscription/SubscriptionCalculator.php
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Subscription;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use App\DataMapper\InvoiceItem;
|
||||||
|
use App\Factory\InvoiceFactory;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use App\Helpers\Invoice\ProRata;
|
||||||
|
use App\Repositories\InvoiceRepository;
|
||||||
|
use App\Repositories\SubscriptionRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SubscriptionCalculator.
|
||||||
|
*/
|
||||||
|
class SubscriptionCalculator
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public function __construct(public Subscription $subscription){}
|
||||||
|
|
||||||
|
public function buildPurchaseInvoice(array $context): Invoice
|
||||||
|
{
|
||||||
|
|
||||||
|
$invoice_repo = new InvoiceRepository();
|
||||||
|
$subscription_repo = new SubscriptionRepository();
|
||||||
|
|
||||||
|
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
||||||
|
$invoice->subscription_id = $this->subscription->id;
|
||||||
|
$invoice->client_id = $this->decodePrimaryKey($context['client_id']);
|
||||||
|
$invoice->is_proforma = true;
|
||||||
|
$invoice->number = "####" . ctrans('texts.subscription') . "_" . now()->format('Y-m-d') . "_" . rand(0, 100000);
|
||||||
|
$invoice->line_items = $this->buildItems($context);
|
||||||
|
|
||||||
|
if(isset($context['valid_coupon']) && $context['valid_coupon']) {
|
||||||
|
$invoice->discount = $this->subscription->promo_discount;
|
||||||
|
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $invoice_repo->save([], $invoice);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Line Items
|
||||||
|
*
|
||||||
|
* @param array $context
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function buildItems(array $context): array
|
||||||
|
{
|
||||||
|
|
||||||
|
$bundle = $context['bundle'];
|
||||||
|
|
||||||
|
$recurring = array_merge(isset($bundle['recurring_products']) ? $bundle['recurring_products'] : [], isset($bundle['optional_recurring_products']) ? $bundle['optional_recurring_products'] : []);
|
||||||
|
$one_time = array_merge(isset($bundle['one_time_products']) ? $bundle['one_time_products'] : [], isset($bundle['optional_one_time_products']) ? $bundle['optional_one_time_products'] : []);
|
||||||
|
|
||||||
|
$items = array_filter(array_merge($recurring, $one_time), function ($product) {
|
||||||
|
return $product['quantity'] >= 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return collect($items)->map(function ($item){
|
||||||
|
|
||||||
|
$line_item = new InvoiceItem();
|
||||||
|
$line_item->product_key = $item['product']['product_key'];
|
||||||
|
$line_item->quantity = (float) $item['quantity'];
|
||||||
|
$line_item->cost = (float) ['product']['price'];
|
||||||
|
$line_item->notes = $item['product']['notes'];
|
||||||
|
|
||||||
|
return $line_item;
|
||||||
|
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the user is currently up
|
||||||
|
* to date with their payments for
|
||||||
|
* a given recurring invoice
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isPaidUp(Invoice $invoice): bool
|
||||||
|
{
|
||||||
|
$outstanding_invoices_exist = Invoice::query()->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||||
|
->where('subscription_id', $invoice->subscription_id)
|
||||||
|
->where('client_id', $invoice->client_id)
|
||||||
|
->where('balance', '>', 0)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
return ! $outstanding_invoices_exist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function calcUpgradePlan(Invoice $invoice)
|
||||||
|
{
|
||||||
|
//set the starting refund amount
|
||||||
|
$refund_amount = 0;
|
||||||
|
|
||||||
|
$refund_invoice = false;
|
||||||
|
|
||||||
|
//are they paid up to date.
|
||||||
|
|
||||||
|
//yes - calculate refund
|
||||||
|
if ($this->isPaidUp($invoice)) {
|
||||||
|
$refund_invoice = $this->getRefundInvoice($invoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($refund_invoice) {
|
||||||
|
/** @var \App\Models\Subscription $subscription **/
|
||||||
|
$subscription = Subscription::find($invoice->subscription_id);
|
||||||
|
$pro_rata = new ProRata();
|
||||||
|
|
||||||
|
$to_date = $subscription->service()->getNextDateForFrequency(Carbon::parse($refund_invoice->date), $subscription->frequency_id);
|
||||||
|
|
||||||
|
$refund_amount = $pro_rata->refund($refund_invoice->amount, now(), $to_date, $subscription->frequency_id);
|
||||||
|
|
||||||
|
$charge_amount = $pro_rata->charge($this->subscription->price, now(), $to_date, $this->subscription->frequency_id);
|
||||||
|
|
||||||
|
return $charge_amount - $refund_amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
//no - return full freight charge.
|
||||||
|
return $this->subscription->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function executeUpgradePlan() {}
|
||||||
|
|
||||||
|
private function getRefundInvoice(Invoice $invoice)
|
||||||
|
{
|
||||||
|
return Invoice::where('subscription_id', $invoice->subscription_id)
|
||||||
|
->where('client_id', $invoice->client_id)
|
||||||
|
->where('is_deleted', 0)
|
||||||
|
->orderBy('id', 'desc')
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user