From 223793949192015be7ed5507342a2505ffe5df2b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 14 Apr 2021 12:40:16 +1000 Subject: [PATCH 1/5] 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 From 986e7fee86ff40773dbdf368e0159edbc493098c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 14 Apr 2021 12:51:22 +1000 Subject: [PATCH 2/5] Add validation rules for recurring productS --- .../Subscription/StoreSubscriptionRequest.php | 5 ++-- .../UpdateSubscriptionRequest.php | 25 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/Http/Requests/Subscription/StoreSubscriptionRequest.php b/app/Http/Requests/Subscription/StoreSubscriptionRequest.php index 7e9622f30c89..918b970ada30 100644 --- a/app/Http/Requests/Subscription/StoreSubscriptionRequest.php +++ b/app/Http/Requests/Subscription/StoreSubscriptionRequest.php @@ -35,10 +35,11 @@ class StoreSubscriptionRequest extends Request public function rules() { $rules = [ - 'product_id' => ['sometimes'], + 'product_ids' => ['sometimes'], + 'recurring_product_ids' => ['sometimes'], 'assigned_user_id' => ['sometimes'], 'is_recurring' => ['sometimes'], - 'frequency_id' => ['sometimes'], + 'frequency_id' => ['required_with:recurring_product_ids'], 'auto_bill' => ['sometimes'], 'promo_code' => ['sometimes'], 'promo_discount' => ['sometimes'], diff --git a/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php b/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php index bd5e45aebe43..fdf72b188633 100644 --- a/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php +++ b/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php @@ -13,6 +13,7 @@ namespace App\Http\Requests\Subscription; use App\Http\Requests\Request; use App\Utils\Traits\ChecksEntityStatus; +use Illuminate\Validation\Rule; class UpdateSubscriptionRequest extends Request { @@ -35,12 +36,32 @@ class UpdateSubscriptionRequest extends Request */ public function rules() { - $rules = [ - // + $rules = [ + 'product_ids' => ['sometimes'], + 'recurring_product_ids' => ['sometimes'], + 'assigned_user_id' => ['sometimes'], + 'is_recurring' => ['sometimes'], + 'frequency_id' => ['required_with:recurring_product_ids'], + 'auto_bill' => ['sometimes'], + 'promo_code' => ['sometimes'], + 'promo_discount' => ['sometimes'], + 'is_amount_discount' => ['sometimes'], + 'allow_cancellation' => ['sometimes'], + 'per_set_enabled' => ['sometimes'], + 'min_seats_limit' => ['sometimes'], + 'max_seats_limit' => ['sometimes'], + 'trial_enabled' => ['sometimes'], + 'trial_duration' => ['sometimes'], + 'allow_query_overrides' => ['sometimes'], + 'allow_plan_changes' => ['sometimes'], + 'refund_period' => ['sometimes'], + 'webhook_configuration' => ['array'], + 'name' => ['required', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)->ignore($this->subscription->id)] ]; return $this->globalRules($rules); + } protected function prepareForValidation() From aabe5683e19b3efc3e12ae0d535eed15d6595503 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 14 Apr 2021 14:41:04 +1000 Subject: [PATCH 3/5] Refactoring subscriptions --- app/Console/Commands/CreateSingleAccount.php | 4 + app/Http/Livewire/BillingPortalPurchase.php | 32 ++++++- app/Jobs/Mail/NinjaMailerJob.php | 4 +- .../Subscription/SubscriptionService.php | 48 ++++++++--- app/Services/Subscription/ZeroCostProduct.php | 84 +++++++++++++++++++ app/Utils/Traits/SubscriptionHooker.php | 6 +- resources/lang/en/texts.php | 2 +- .../billing-portal-purchase.blade.php | 7 ++ 8 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 app/Services/Subscription/ZeroCostProduct.php diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index c150d7dcfe17..2a4dab445893 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -32,6 +32,7 @@ use App\Models\Expense; use App\Models\Product; use App\Models\Project; use App\Models\Quote; +use App\Models\RecurringInvoice; use App\Models\Task; use App\Models\User; use App\Models\Vendor; @@ -255,6 +256,7 @@ class CreateSingleAccount extends Command $sub->recurring_product_ids = "{$p1->hashed_id}"; $sub->webhook_configuration = $webhook_config; $sub->allow_plan_changes = true; + $sub->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY; $sub->save(); $sub = SubscriptionFactory::create($company->id, $user->id); @@ -263,6 +265,7 @@ class CreateSingleAccount extends Command $sub->recurring_product_ids = "{$p2->hashed_id}"; $sub->webhook_configuration = $webhook_config; $sub->allow_plan_changes = true; + $sub->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY; $sub->save(); $sub = SubscriptionFactory::create($company->id, $user->id); @@ -271,6 +274,7 @@ class CreateSingleAccount extends Command $sub->recurring_product_ids = "{$p3->hashed_id}"; $sub->webhook_configuration = $webhook_config; $sub->allow_plan_changes = true; + $sub->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY; $sub->save(); } diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php index 8610ea99f438..c864cc666f3d 100644 --- a/app/Http/Livewire/BillingPortalPurchase.php +++ b/app/Http/Livewire/BillingPortalPurchase.php @@ -112,6 +112,7 @@ class BillingPortalPurchase extends Component 'show_loading_bar' => false, 'not_eligible' => null, 'not_eligible_message' => null, + 'payment_required' => true, ]; /** @@ -269,8 +270,11 @@ class BillingPortalPurchase extends Component return $this; } - - $this->steps['fetched_payment_methods'] = true; + + if((int)$this->subscription->price == 0) + $this->steps['payment_required'] = false; + else + $this->steps['fetched_payment_methods'] = true; $this->methods = $contact->client->service()->getPaymentMethods($this->price); @@ -357,6 +361,30 @@ class BillingPortalPurchase extends Component 'email' => $this->email ?? $this->contact->email, 'quantity' => $this->quantity, 'contact_id' => $this->contact->id, + 'client_id' => $this->contact->client->id, + ]); + } + + public function handlePaymentNotRequired() + { + + $is_eligible = $this->subscription->service()->isEligible($this->contact); + + if ($is_eligible['status_code'] != 200) { + $this->steps['not_eligible'] = true; + $this->steps['not_eligible_message'] = $is_eligible['exception']['message']; + $this->steps['show_loading_bar'] = false; + + return; + } + + + return $this->subscription->service()->handleNoPaymentRequired([ + 'email' => $this->email ?? $this->contact->email, + 'quantity' => $this->quantity, + 'contact_id' => $this->contact->id, + 'client_id' => $this->contact->client->id, + 'coupon' => '', ]); } diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 23aafa6c51ea..c82f16605f91 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -105,10 +105,10 @@ class NinjaMailerJob implements ShouldQueue switch ($class) { case Invoice::class: - event(new InvoiceWasEmailedAndFailed($this->nmo->invitation, $this->nmo->company, $message, $this->nmo->reminder_template, Ninja::eventVars(auth()->user()->id))); + event(new InvoiceWasEmailedAndFailed($this->nmo->invitation, $this->nmo->company, $message, $this->nmo->reminder_template, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); break; case Payment::class: - event(new PaymentWasEmailedAndFailed($this->nmo->entity, $this->nmo->company, $message, Ninja::eventVars(auth()->user()->id))); + event(new PaymentWasEmailedAndFailed($this->nmo->entity, $this->nmo->company, $message, Ninja::eventVars(auth()->user ? auth()->user()->id : null))); break; default: # code... diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index 513cb97ccedc..7c02d9aa6347 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -29,6 +29,7 @@ use App\Models\SystemLog; use App\Repositories\InvoiceRepository; use App\Repositories\RecurringInvoiceRepository; use App\Repositories\SubscriptionRepository; +use App\Services\Subscription\ZeroCostProduct; use App\Utils\Ninja; use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\MakesHash; @@ -93,12 +94,10 @@ class SubscriptionService $response = $this->triggerWebhook($context); - nlog($response); + // 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']); + $this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id); - return redirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id); } else { @@ -114,10 +113,7 @@ class SubscriptionService //execute any webhooks $this->triggerWebhook($context); - 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/invoices/'.$this->encodePrimaryKey($payment_hash->fee_invoice_id)); + $this->handleRedirect('/client/invoices/'.$this->encodePrimaryKey($payment_hash->fee_invoice_id)); } } @@ -417,6 +413,7 @@ class SubscriptionService } + public function createInvoice($data): ?\App\Models\Invoice { @@ -438,7 +435,7 @@ class SubscriptionService } - private function convertInvoiceToRecurring($client_id) :RecurringInvoice + public function convertInvoiceToRecurring($client_id) :RecurringInvoice { $subscription_repo = new SubscriptionRepository(); @@ -480,7 +477,8 @@ class SubscriptionService else { $status = $response->getStatusCode(); - $response_body = $response->getBody(); + + //$response_body = $response->getReasonPhrase(); $body = array_merge($body, ['status' => $status, 'response_body' => $response_body]); } @@ -580,6 +578,36 @@ class SubscriptionService } } + + /** + * 'email' => $this->email ?? $this->contact->email, + * 'quantity' => $this->quantity, + * 'contact_id' => $this->contact->id, + */ + public function handleNoPaymentRequired(array $data) + { + $context = (new ZeroCostProduct($this->subscription, $data))->run(); + + // Forward payload to webhook + if(array_key_exists('context', $context)) + $response = $this->triggerWebhook($context); + + // Hit the redirect + return $this->handleRedirect($context['redirect_url']); + + } + + /** + * Handles redirecting the user + */ + private function handleRedirect($default_redirect) + { + + if(array_key_exists('return_url', $this->subscription->webhook_configuration) && strlen($this->subscription->webhook_configuration['return_url']) >=1) + return redirect($this->subscription->webhook_configuration['return_url']); + + return redirect($default_redirect); + } } diff --git a/app/Services/Subscription/ZeroCostProduct.php b/app/Services/Subscription/ZeroCostProduct.php new file mode 100644 index 000000000000..c132ee4b5f0e --- /dev/null +++ b/app/Services/Subscription/ZeroCostProduct.php @@ -0,0 +1,84 @@ + $this->email ?? $this->contact->email, + 'quantity' => $this->quantity, + 'contact_id' => $this->contact->id, + 'client_id' => $this->contact->client->id, + ]; + */ + public function __construct(Subscription $subscription, array $data) + { + $this->subscription = $subscription; + + $this->data = $data; + + } + + public function run() + { + //create a zero dollar invoice. + + $invoice = $this->subscription->service()->createInvoice($this->data); + + $invoice->service() + ->markPaid() + ->save(); + + $redirect_url = "/client/invoices/{$invoice->hashed_id}"; + + //create a recurring zero dollar invoice attached to this subscription. + + if(strlen($this->subscription->recurring_product_ids) >=1){ + + $recurring_invoice = $this->subscription->service()->convertInvoiceToRecurring($this->data['client_id']); + $recurring_invoice_repo = new RecurringInvoiceRepository(); + + $recurring_invoice->next_send_date = now(); + $recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice); + $recurring_invoice->next_send_date = $recurring_invoice->nextSendDate(); + + /* Start the recurring service */ + $recurring_invoice->service() + ->start() + ->save(); + + $context = [ + 'context' => 'recurring_purchase', + 'recurring_invoice' => $recurring_invoice->hashed_id, + 'invoice' => $invoice->hashed_id, + 'client' => $recurring_invoice->client->hashed_id, + 'subscription' => $this->subscription->hashed_id, + 'contact' => auth('contact')->user()->hashed_id, + 'redirect_url' => "/client/recurring_invoices/{$recurring_invoice->hashed_id}", + ]; + + return $context; + } + + return ['redirect_url' => $redirect_url]; + } + +} diff --git a/app/Utils/Traits/SubscriptionHooker.php b/app/Utils/Traits/SubscriptionHooker.php index ced93bbb2f21..29f187bf4658 100644 --- a/app/Utils/Traits/SubscriptionHooker.php +++ b/app/Utils/Traits/SubscriptionHooker.php @@ -40,11 +40,13 @@ trait SubscriptionHooker RequestOptions::JSON => ['body' => $body], RequestOptions::ALLOW_REDIRECTS => false ]); - return $response; + return array_merge($body, ['exception' => json_decode($response->getBody(),true), 'status_code' => $response->getStatusCode()]); } catch(\Exception $e) { - $body = array_merge($body, ['exception' => $e->getMessage()]); + //; + // dd($e); + $body = array_merge($body, ['exception' => ['message' => $e->getMessage(), 'status_code' => 500]]); return $body; } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 09945112a32d..6429e4e8ce12 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4210,7 +4210,7 @@ $LANG = array( 'activity_83' => ':user deleted subscription :subscription', 'activity_84' => ':user restored subscription :subscription', 'amount_greater_than_balance_v5' => 'The amount is greater than the invoice balance. You cannot overpay an invoice.', - + 'click_to_continue' => 'Click to continue', ); return $LANG; diff --git a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchase.blade.php b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchase.blade.php index 8836fd65aa41..9b9ea5632f5b 100644 --- a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchase.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchase.blade.php @@ -135,6 +135,13 @@ @endif + @elseif(!$steps['payment_required']) +
+ @csrf + +
@elseif($steps['show_start_trial'])
@csrf From bb0d91a30fe808f4bd31525759cb499c0275e2f1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 14 Apr 2021 14:49:53 +1000 Subject: [PATCH 4/5] Fixes for HTMLEngine --- VERSION.txt | 2 +- app/Utils/HtmlEngine.php | 2 +- config/ninja.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 6b93b22dd885..3deddf16c2da 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.1.44 \ No newline at end of file +5.1.45 \ No newline at end of file diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 0fe5bf86c501..6f891eeabe4f 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -375,7 +375,7 @@ class HtmlEngine $data['$entity_footer'] = ['value' => $this->entity->footer, 'label' => '']; $data['$page_size'] = ['value' => $this->settings->page_size, 'label' => '']; - $data['$page_layout'] = ['value' => $this->settings->page_layout, 'label' => '']; + $data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => '']; $arrKeysLength = array_map('strlen', array_keys($data)); array_multisort($arrKeysLength, SORT_DESC, $data); diff --git a/config/ninja.php b/config/ninja.php index dfa73b5ec103..4c40162c8ce6 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', ''), - 'app_version' => '5.1.44', - 'app_tag' => '5.1.44-release', + 'app_version' => '5.1.45', + 'app_tag' => '5.1.45-release', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), From 7d75bdb0700998a3638a47de57ed7863cfded238 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 14 Apr 2021 14:52:42 +1000 Subject: [PATCH 5/5] minor fixes for tests --- tests/Feature/SubscriptionApiTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Feature/SubscriptionApiTest.php b/tests/Feature/SubscriptionApiTest.php index 4c5148c410a6..2ef470bbe649 100644 --- a/tests/Feature/SubscriptionApiTest.php +++ b/tests/Feature/SubscriptionApiTest.php @@ -12,6 +12,7 @@ namespace Tests\Feature; use App\Models\Product; +use App\Models\RecurringInvoice; use App\Models\Subscription; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; @@ -91,6 +92,7 @@ class SubscriptionApiTest extends TestCase $product = Product::factory()->create([ 'company_id' => $this->company->id, 'user_id' => $this->user->id, + 'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY, ]); $response1 = $this