From 223793949192015be7ed5507342a2505ffe5df2b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 14 Apr 2021 12:40:16 +1000 Subject: [PATCH] Subscriptions --- app/Console/Commands/CreateSingleAccount.php | 20 +- .../SubscriptionPlanSwitchController.php | 3 +- app/Http/Livewire/BillingPortalPurchase.php | 1 + app/Http/Livewire/SubscriptionPlanSwitch.php | 16 +- .../Subscription/SubscriptionService.php | 211 ++++++++++++++---- .../ninja2020/subscriptions/switch.blade.php | 2 +- 6 files changed, 204 insertions(+), 49 deletions(-) diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index 080a95b2ceaa..c150d7dcfe17 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -227,12 +227,22 @@ class CreateSingleAccount extends Command 'user_id' => $user->id, 'company_id' => $company->id, 'product_key' => 'enterprise_plan', - 'notes' => 'The Pro Plan', + 'notes' => 'The Enterprise Plan', 'cost' => 10, 'price' => 10, 'quantity' => 1, ]); + $p3 = Product::factory()->create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + 'product_key' => 'free_plan', + 'notes' => 'The Free Plan', + 'cost' => 0, + 'price' => 0, + 'quantity' => 1, + ]); + $webhook_config = [ 'post_purchase_url' => 'http://ninja.test:8000/api/admin/plan', 'post_purchase_rest_method' => 'POST', @@ -254,6 +264,14 @@ class CreateSingleAccount extends Command $sub->webhook_configuration = $webhook_config; $sub->allow_plan_changes = true; $sub->save(); + + $sub = SubscriptionFactory::create($company->id, $user->id); + $sub->name = "Free Plan"; + $sub->group_id = $gs->id; + $sub->recurring_product_ids = "{$p3->hashed_id}"; + $sub->webhook_configuration = $webhook_config; + $sub->allow_plan_changes = true; + $sub->save(); } private function createClient($company, $user) diff --git a/app/Http/Controllers/ClientPortal/SubscriptionPlanSwitchController.php b/app/Http/Controllers/ClientPortal/SubscriptionPlanSwitchController.php index d745f61630b3..7adf172baa1a 100644 --- a/app/Http/Controllers/ClientPortal/SubscriptionPlanSwitchController.php +++ b/app/Http/Controllers/ClientPortal/SubscriptionPlanSwitchController.php @@ -17,6 +17,7 @@ use App\Http\Requests\ClientPortal\Subscriptions\ShowPlanSwitchRequest; use App\Models\RecurringInvoice; use App\Models\Subscription; use Illuminate\Http\Request; +use Illuminate\Support\Str; class SubscriptionPlanSwitchController extends Controller { @@ -30,7 +31,7 @@ class SubscriptionPlanSwitchController extends Controller */ public function index(ShowPlanSwitchRequest $request, RecurringInvoice $recurring_invoice, Subscription $target) { - + $amount = $recurring_invoice->subscription ->service() ->calculateUpgradePrice($recurring_invoice, $target); diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php index 820512ba49b7..8610ea99f438 100644 --- a/app/Http/Livewire/BillingPortalPurchase.php +++ b/app/Http/Livewire/BillingPortalPurchase.php @@ -339,6 +339,7 @@ class BillingPortalPurchase extends Component 'email' => $this->email ?? $this->contact->email, 'client_id' => $this->contact->client->id, 'invoice_id' => $this->invoice->id, + 'context' => 'purchase', now()->addMinutes(60)] ); diff --git a/app/Http/Livewire/SubscriptionPlanSwitch.php b/app/Http/Livewire/SubscriptionPlanSwitch.php index b08ebb24617c..078096ab7f61 100644 --- a/app/Http/Livewire/SubscriptionPlanSwitch.php +++ b/app/Http/Livewire/SubscriptionPlanSwitch.php @@ -14,6 +14,7 @@ namespace App\Http\Livewire; use App\Models\ClientContact; use App\Models\Subscription; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; use Livewire\Component; @@ -74,7 +75,7 @@ class SubscriptionPlanSwitch extends Component { $this->total = $this->amount; - $this->methods = $this->contact->client->service()->getPaymentMethods(100); + $this->methods = $this->contact->client->service()->getPaymentMethods($this->amount); $this->hash = Str::uuid()->toString(); } @@ -83,12 +84,23 @@ class SubscriptionPlanSwitch extends Component { $this->state['show_loading_bar'] = true; - $this->state['invoice'] = $this->subscription->service()->createChangePlanInvoice([ + $this->state['invoice'] = $this->target->service()->createChangePlanInvoice([ 'recurring_invoice' => $this->recurring_invoice, 'subscription' => $this->subscription, 'target' => $this->target, + 'hash' => $this->hash, ]); + Cache::put($this->hash, [ + 'subscription_id' => $this->target->id, + 'target_id' => $this->target->id, + 'recurring_invoice' => $this->recurring_invoice->id, + 'client_id' => $this->recurring_invoice->client->id, + 'invoice_id' => $this->state['invoice']->id, + 'context' => 'change_plan', + now()->addMinutes(60)] + ); + $this->state['payment_initialised'] = true; $this->emit('beforePaymentEventsCompleted'); diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index 795c4cce5866..513cb97ccedc 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -61,6 +61,10 @@ class SubscriptionService throw new \Exception("Illegal entrypoint into method, payload must contain billing context"); } + if($payment_hash->data->billing_context->context == 'change_plan') { + return $this->handlePlanChange($payment_hash); + }; + // if we have a recurring product - then generate a recurring invoice if(strlen($this->subscription->recurring_product_ids) >=1){ @@ -84,6 +88,7 @@ class SubscriptionService 'invoice' => $this->encodePrimaryKey($payment_hash->fee_invoice_id), 'client' => $recurring_invoice->client->hashed_id, 'subscription' => $this->subscription->hashed_id, + 'contact' => auth('contact')->user()->hashed_id, ]; $response = $this->triggerWebhook($context); @@ -217,15 +222,43 @@ class SubscriptionService } + /** + * We refund unused days left. + * + * @param Invoice $invoice + * @return float + */ private function calculateProRataRefund($invoice) :float { - //determine the start date $start_date = Carbon::parse($invoice->date); $current_date = now(); $days_to_refund = $start_date->diffInDays($current_date); + + $days_in_frequency = $this->getDaysInFrequency(); + + $pro_rata_refund = round((($days_in_frequency - $days_to_refund)/$days_in_frequency) * $invoice->amount ,2); + + return $pro_rata_refund; + } + + /** + * We only charge for the used days + * + * @param Invoice $invoice + * @return float + */ + private function calculateProRataCharge($invoice) :float + { + + $start_date = Carbon::parse($invoice->date); + + $current_date = now(); + + $days_to_refund = $start_date->diffInDays($current_date); + $days_in_frequency = $this->getDaysInFrequency(); $pro_rata_refund = round(($days_to_refund/$days_in_frequency) * $invoice->amount ,2); @@ -235,6 +268,7 @@ class SubscriptionService public function createChangePlanInvoice($data) { + $recurring_invoice = $data['recurring_invoice']; //Data array structure /** * [ @@ -244,40 +278,145 @@ class SubscriptionService * ] */ - $outstanding_invoice = $recurring_invoice->invoices() - ->where('is_deleted', 0) - ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) - ->where('balance', '>', 0) - ->first(); + // $outstanding_invoice = $recurring_invoice->invoices() + // ->where('is_deleted', 0) + // ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) + // ->where('balance', '>', 0) + // ->first(); - $pro_rata_refund = null; + $pro_rata_charge_amount = 0; + $pro_rata_refund_amount = 0; - // we calculate the pro rata refund for this invoice. - if($outstanding_invoice) + // // We calculate the pro rata charge for this invoice. + // if($outstanding_invoice) + // { + // } + + $last_invoice = $recurring_invoice->invoices() + ->where('is_deleted', 0) + ->orderBy('id', 'desc') + ->first(); + + //$last_invoice may not be here! + + if(!$last_invoice) { + $data = [ + 'client_id' => $recurring_invoice->client_id, + 'coupon' => '', + ]; + + return $this->createInvoice($data)->service()->markSent()->fillDefaults()->save(); + + } + else if($last_invoice->balance > 0) { - // $pro_rata_refund = $this->calculateProRataRefund($out + $pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice); + } + else + { + $pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice) * -1; + } + + $total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price; + + if($total_payable > 0) + { + return $this->proRataInvoice($pro_rata_refund_amount, $data['subscription'], $data['target']); + } + else + { + //create credit } - //logic - - // Is the user paid up to date? ie are there any outstanding invoices for this subscription - // User in arrears. - - - // User paid up to date (in credit!) - - //generate credit amount. - // - //generate new billable amount - // - - //if billable amount is LESS than 0 -> generate a credit and pass through. - // - //if billable amoun is GREATER than 0 -> gener return Invoice::where('status_id', Invoice::STATUS_SENT)->first(); } + /** + * Response from payment service on return from a plan change + * + */ + private function handlePlanChange($payment_hash) + { + + //payment has been made. + // + //new subscription starts today - delete old recurring invoice. + + $old_subscription_recurring_invoice = RecurringInvoice::find($payment_hash->data->billing_context->recurring_invoice); + $old_subscription_recurring_invoice->service()->stop()->save(); + + $recurring_invoice_repo = new RecurringInvoiceRepository(); + $recurring_invoice_repo->archive($old_subscription_recurring_invoice); + + $recurring_invoice = $this->convertInvoiceToRecurring($payment_hash->payment->client_id); + $recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice); + $recurring_invoice->next_send_date = now(); + $recurring_invoice->next_send_date = $recurring_invoice->nextSendDate(); + + /* Start the recurring service */ + $recurring_invoice->service() + ->start() + ->save(); + + $context = [ + 'context' => 'change_plan', + 'recurring_invoice' => $recurring_invoice->hashed_id, + 'invoice' => $this->encodePrimaryKey($payment_hash->fee_invoice_id), + 'client' => $recurring_invoice->client->hashed_id, + 'subscription' => $this->subscription->hashed_id, + 'contact' => auth('contact')->user()->hashed_id, + ]; + + $response = $this->triggerWebhook($context); + + nlog($response); + + if(array_key_exists('post_purchase_url', $this->subscription->webhook_configuration) && strlen($this->subscription->webhook_configuration['post_purchase_url']) >=1) + return redirect($this->subscription->webhook_configuration['post_purchase_url']); + + return redirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id); + + } + + public function handlePlanChangeNoPayment() + { + + } + + /** + * 'client_id' => 2, + 'date' => '2021-04-13', + 'invitations' => + 'user_input_promo_code' => NULL, + 'coupon' => '', + 'quantity' => 1, + */ + private function proRataInvoice($refund_amount, $subscription, $target) + { + $subscription_repo = new SubscriptionRepository(); + $invoice_repo = new InvoiceRepository(); + + $line_items = $subscription_repo->generateLineItems($target); + + $item = new InvoiceItem; + $item->quantity = 1; + $item->product_key = ctrans('texts.refund'); + $item->notes = ctrans('texts.refund') . ":" .$subscription->name; + $item->cost = $refund_amount; + + $line_items[] = $item; + + $data = [ + 'client_id' => $subscription->client_id, + 'quantity' => 1, + 'date' => now()->format('Y-m-d'), + ]; + + return $invoice_repo->save($data, $invoice)->service()->markSent()->fillDefaults()->save(); + + } + public function createInvoice($data): ?\App\Models\Invoice { @@ -401,11 +540,6 @@ class SubscriptionService ->get(); } - public function completePlanChange(PaymentHash $paymentHash) - { - // .. handle redirect, after upgrade redirects, etc.. - } - public function handleCancellation() { dd('Cancelling using SubscriptionService'); @@ -413,19 +547,6 @@ class SubscriptionService // .. } - /** - * Get pro rata calculation between subscriptions. - * - * @param Subscription $current - * @param Subscription $target - */ - public function getPriceBetweenSubscriptions(Subscription $current, Subscription $target): int - { - // Calculate the pro rata. Return negative value if credits needed. - - return 1; - } - private function getDaysInFrequency() { @@ -459,4 +580,6 @@ class SubscriptionService } } + + } diff --git a/resources/views/portal/ninja2020/subscriptions/switch.blade.php b/resources/views/portal/ninja2020/subscriptions/switch.blade.php index dd688719103e..8d1b85dd6150 100644 --- a/resources/views/portal/ninja2020/subscriptions/switch.blade.php +++ b/resources/views/portal/ninja2020/subscriptions/switch.blade.php @@ -42,7 +42,7 @@ - @livewire('subscription-plan-switch', compact('subscription', 'target', 'contact')) + @livewire('subscription-plan-switch', compact('recurring_invoice', 'subscription', 'target', 'contact', 'amount')) @endsection