From 81e8997e2cc2054d050bd9691abed9ab2c94071b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 10 Dec 2022 10:54:34 +1100 Subject: [PATCH 1/4] Minor cleanup for gateway model --- app/Models/GatewayType.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index 9802edae51fa..cc4d0a998fe7 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -11,8 +11,6 @@ namespace App\Models; -use function Symfony\Component\String\s; - class GatewayType extends StaticModel { public $timestamps = false; From 9a0a55d356b0d87182e86a7f01704e1c9f0620e0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 10 Dec 2022 12:28:54 +1100 Subject: [PATCH 2/4] OTP for subscriptions --- app/Http/Livewire/BillingPortalPurchasev2.php | 42 +++++++++++-- .../Requests/Report/GenericReportRequest.php | 1 - app/Mail/Subscription/OtpCode.php | 63 +++++++++++++++++++ lang/en/texts.php | 4 +- 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 app/Mail/Subscription/OtpCode.php diff --git a/app/Http/Livewire/BillingPortalPurchasev2.php b/app/Http/Livewire/BillingPortalPurchasev2.php index 35933f13d043..b8073c5b203d 100644 --- a/app/Http/Livewire/BillingPortalPurchasev2.php +++ b/app/Http/Livewire/BillingPortalPurchasev2.php @@ -17,6 +17,7 @@ use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Libraries\MultiDB; use App\Mail\ContactPasswordlessLogin; +use App\Mail\Subscription\OtpCode; use App\Models\Client; use App\Models\ClientContact; use App\Models\Invoice; @@ -229,12 +230,22 @@ class BillingPortalPurchasev2 extends Component $this->resetValidation('login'); } - public function handleLogin() + public function handleLogin($user_code) { + $this->resetErrorBag('login'); + $this->resetValidation('login'); + $code = Cache::get("subscriptions:otp:{$this->email}"); - $this->validateOnly('login', ['login' => ['required',Rule::in([$code])]], ['login' => ctrans('texts.invalid_code')]); + // $this->validateOnly('login', ['login' => 'required'], ['login' => ctrans('texts.invalid_code')]); + + if($user_code != $code){ + nlog($code); + nlog($user_code); + $errors = $this->getErrorBag(); + $errors->add('login', ctrans('texts.invalid_code')); + } $contact = ClientContact::where('email', $this->email)->first(); @@ -243,9 +254,13 @@ class BillingPortalPurchasev2 extends Component $this->contact = $contact; } else { - + $this->createClientContact(); } + } + + public function showClientRequiredFields() + { } @@ -259,8 +274,25 @@ class BillingPortalPurchasev2 extends Component Cache::put($email_hash, $rand, 120); + $this->emailOtpCode($rand); } + private function emailOtpCode($code) + { + + $cc = new ClientContact(); + $cc->email = $this->email; + + $nmo = new NinjaMailerObject; + $nmo->mailable = new OtpCode($this->subscription->company, $this->contact, $code); + $nmo->company = $this->subscription->company; + $nmo->settings = $this->subscription->company->settings; + $nmo->to_user = $cc; + NinjaMailerJob::dispatch($nmo); + + } + + /** * Handle a coupon being entered into the checkout */ @@ -411,9 +443,9 @@ class BillingPortalPurchasev2 extends Component $this->contact = $client->fresh()->contacts()->first(); Auth::guard('contact')->loginUsingId($this->contact->id, true); + return $this; } - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public function updated($propertyName) { @@ -424,6 +456,8 @@ class BillingPortalPurchasev2 extends Component } + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + public function rules() { $rules = [ diff --git a/app/Http/Requests/Report/GenericReportRequest.php b/app/Http/Requests/Report/GenericReportRequest.php index 9cfddf60f433..d7ad07152b8f 100644 --- a/app/Http/Requests/Report/GenericReportRequest.php +++ b/app/Http/Requests/Report/GenericReportRequest.php @@ -28,7 +28,6 @@ class GenericReportRequest extends Request public function rules() { - nlog($this->date_range); return [ 'date_range' => 'bail|required|string', diff --git a/app/Mail/Subscription/OtpCode.php b/app/Mail/Subscription/OtpCode.php new file mode 100644 index 000000000000..8f7424f34547 --- /dev/null +++ b/app/Mail/Subscription/OtpCode.php @@ -0,0 +1,63 @@ +company = $company; + $this->contact = $contact; + $this->code = $code; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + App::setLocale($this->company->locale()); + + return $this->from(config('mail.from.address'), config('mail.from.name')) + ->subject(ctrans('texts.otp_code_subject')) + ->text('email.admin.generic_text') + ->view('email.admin.generic') + ->with([ + 'settings' => $this->company->settings, + 'logo' => $this->company->present()->logo(), + 'title' => ctrans('texts.otp_code_subject'), + 'content' => ctrans('texts.otp_code_body', ['code' => $this->code]), + 'whitelabel' => $this->company->account->isPaid(), + ]); + } +} diff --git a/lang/en/texts.php b/lang/en/texts.php index 090ba0f2dde9..a92f17bb603f 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -4895,7 +4895,9 @@ $LANG = array( 'delete_bank_account' => 'Delete Bank Account', 'archive_transaction' => 'Archive Transaction', 'delete_transaction' => 'Delete Transaction', - 'otp_code_message' => 'Enter the code emailed.' + 'otp_code_message' => 'Enter the code emailed.', + 'otp_code_subject' => 'Your one time passcode code', + 'otp_code_body' => 'Your one time passcode is :code', ); return $LANG; From 21bec86fdae3e5a3500a7e20ee44d9fdade90a26 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 10 Dec 2022 14:25:46 +1100 Subject: [PATCH 3/4] Subscriptions v2 --- app/Http/Livewire/BillingPortalPurchasev2.php | 93 ++++++++++--------- app/PaymentDrivers/Stripe/Charge.php | 1 - .../billing-portal-purchasev2.blade.php | 33 ++++++- 3 files changed, 80 insertions(+), 47 deletions(-) diff --git a/app/Http/Livewire/BillingPortalPurchasev2.php b/app/Http/Livewire/BillingPortalPurchasev2.php index b8073c5b203d..2dbfded73243 100644 --- a/app/Http/Livewire/BillingPortalPurchasev2.php +++ b/app/Http/Livewire/BillingPortalPurchasev2.php @@ -192,9 +192,8 @@ class BillingPortalPurchasev2 extends Component public $discount; public $sub_total; public $authenticated = false; - public $otp; public $login; - public $value; + public $float_amount_total; public function mount() { @@ -202,6 +201,7 @@ class BillingPortalPurchasev2 extends Component $this->discount = 0; $this->sub_total = 0; + $this->float_amount_total = 0; $this->data = []; @@ -257,6 +257,10 @@ class BillingPortalPurchasev2 extends Component $this->createClientContact(); } + $this->authenticated = true; + + $this->getPaymentMethods(); + } public function showClientRequiredFields() @@ -264,6 +268,11 @@ class BillingPortalPurchasev2 extends Component } + public function resetEmail() + { + $this->email = null; + } + public function handleEmail() { $this->validateOnly('email', ['email' => 'required|bail|email:rfc']); @@ -415,6 +424,7 @@ class BillingPortalPurchasev2 extends Component $this->total = Number::formatMoney(($this->bundle->sum('total') - $discount), $this->subscription->company); + $this->float_amount_total = ($this->bundle->sum('total') - $discount); } @@ -479,39 +489,39 @@ class BillingPortalPurchasev2 extends Component } - /** - * Handle user authentication - * - * @return $this|bool|void - */ - public function authenticate() - { - $this->validate(); + // /** + // * Handle user authentication + // * + // * @return $this|bool|void + // */ + // public function authenticate() + // { + // $this->validate(); - $contact = ClientContact::where('email', $this->email) - ->where('company_id', $this->subscription->company_id) - ->first(); + // $contact = ClientContact::where('email', $this->email) + // ->where('company_id', $this->subscription->company_id) + // ->first(); - if ($contact && $this->steps['existing_user'] === false) { - return $this->steps['existing_user'] = true; - } + // if ($contact && $this->steps['existing_user'] === false) { + // return $this->steps['existing_user'] = true; + // } - if ($contact && $this->steps['existing_user']) { - $attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]); + // if ($contact && $this->steps['existing_user']) { + // $attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]); - return $attempt - ? $this->getPaymentMethods($contact) - : session()->flash('message', 'These credentials do not match our records.'); - } + // return $attempt + // ? $this->getPaymentMethods($contact) + // : session()->flash('message', 'These credentials do not match our records.'); + // } - $this->steps['existing_user'] = false; + // $this->steps['existing_user'] = false; - $contact = $this->createBlankClient(); + // $contact = $this->createBlankClient(); - if ($contact && $contact instanceof ClientContact) { - $this->getPaymentMethods($contact); - } - } + // if ($contact && $contact instanceof ClientContact) { + // $this->getPaymentMethods($contact); + // } + // } @@ -593,27 +603,26 @@ class BillingPortalPurchasev2 extends Component * @param ClientContact $contact * @return $this */ - protected function getPaymentMethods(ClientContact $contact): self + protected function getPaymentMethods(): self { - Auth::guard('contact')->loginUsingId($contact->id, true); - $this->contact = $contact; + $this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total); - if ($this->subscription->trial_enabled) { - $this->heading_text = ctrans('texts.plan_trial'); - $this->steps['show_start_trial'] = true; + // if ($this->subscription->trial_enabled) { + // $this->heading_text = ctrans('texts.plan_trial'); + // $this->steps['show_start_trial'] = true; - return $this; - } + // return $this; + // } - if ((int)$this->price == 0) - $this->steps['payment_required'] = false; - else - $this->steps['fetched_payment_methods'] = true; + // if ((int)$this->price == 0) + // $this->steps['payment_required'] = false; + // else + // $this->steps['fetched_payment_methods'] = true; - $this->methods = $contact->client->service()->getPaymentMethods($this->price); + // $this->methods = $contact->client->service()->getPaymentMethods($this->price); - $this->heading_text = ctrans('texts.payment_methods'); + // $this->heading_text = ctrans('texts.payment_methods'); return $this; } diff --git a/app/PaymentDrivers/Stripe/Charge.php b/app/PaymentDrivers/Stripe/Charge.php index f023580f661c..ee09ba95fb57 100644 --- a/app/PaymentDrivers/Stripe/Charge.php +++ b/app/PaymentDrivers/Stripe/Charge.php @@ -79,7 +79,6 @@ class Charge 'payment_method' => $cgt->token, 'customer' => $cgt->gateway_customer_reference, 'confirm' => true, - // 'off_session' => true, 'description' => $description, 'metadata' => [ 'payment_hash' => $payment_hash->hash, diff --git a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php index c8e5dd7e2d5e..4c78b9ef2b9c 100644 --- a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php @@ -231,8 +231,28 @@ @if($authenticated) - - @else +
+

{{ $heading_text ?? ctrans('texts.checkout') }}

+ @if (session()->has('message')) + @component('portal.ninja2020.components.message') + {{ session('message') }} + @endcomponent + @endif + @if(count($methods) > 0) +
+ @foreach($methods as $method) + + @endforeach +
+ @endif +
+ @endif + + @if(!$email || $errors->has('email'))
@csrf
@@ -259,8 +279,9 @@ @endif - @if($email && !$errors->has('email')) -
+ @if($email && !$errors->has('email') && !$authenticated) +
+

{{$email}}

{{ ctrans('texts.otp_code_message')}}

@@ -276,6 +297,7 @@ />
+
@error("login") @enderror +
+ +
@endif
From 660371cbf591a3c1928793c0388a07e47005f075 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 10 Dec 2022 16:31:31 +1100 Subject: [PATCH 4/4] Subscriptions v2 - OTP --- app/Http/Livewire/BillingPortalPurchasev2.php | 217 +++++++++--------- .../billing-portal-purchasev2.blade.php | 18 +- 2 files changed, 123 insertions(+), 112 deletions(-) diff --git a/app/Http/Livewire/BillingPortalPurchasev2.php b/app/Http/Livewire/BillingPortalPurchasev2.php index 2dbfded73243..bab495c2bfd6 100644 --- a/app/Http/Livewire/BillingPortalPurchasev2.php +++ b/app/Http/Livewire/BillingPortalPurchasev2.php @@ -194,6 +194,7 @@ class BillingPortalPurchasev2 extends Component public $authenticated = false; public $login; public $float_amount_total; + public $payment_started = false; public function mount() { @@ -205,15 +206,7 @@ class BillingPortalPurchasev2 extends Component $this->data = []; - $this->price = $this->subscription->price; - - if (request()->query('coupon')) { - $this->coupon = request()->query('coupon'); - $this->handleCoupon(); - } - elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){ - $this->price = $this->subscription->promo_price; - } + $this->price = $this->subscription->price; // ? $this->recurring_products = $this->subscription->service()->recurring_products(); $this->products = $this->subscription->service()->products(); @@ -221,6 +214,16 @@ class BillingPortalPurchasev2 extends Component $this->optional_products = $this->subscription->service()->optional_products(); $this->bundle = collect(); + + //every thing below is redundant + + if (request()->query('coupon')) { + $this->coupon = request()->query('coupon'); + $this->handleCoupon(); + } + elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){ + $this->price = $this->subscription->promo_price; + } } @@ -238,13 +241,10 @@ class BillingPortalPurchasev2 extends Component $code = Cache::get("subscriptions:otp:{$this->email}"); - // $this->validateOnly('login', ['login' => 'required'], ['login' => ctrans('texts.invalid_code')]); - if($user_code != $code){ - nlog($code); - nlog($user_code); $errors = $this->getErrorBag(); $errors->add('login', ctrans('texts.invalid_code')); + return $this; } $contact = ClientContact::where('email', $this->email)->first(); @@ -466,6 +466,99 @@ class BillingPortalPurchasev2 extends Component } + /** + * Fetching payment methods from the client. + * + * @return $this + */ + protected function getPaymentMethods(): self + { + + $this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total); + + return $this; + } + + /** + * Middle method between selecting payment method & + * submitting the from to the backend. + * + * @param $company_gateway_id + * @param $gateway_type_id + */ + public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id) + { + $this->company_gateway_id = $company_gateway_id; + $this->payment_method_id = $gateway_type_id; + + $this->handleBeforePaymentEvents(); + } + + /** + * Method to handle events before payments. + * + * @return void + */ + public function handleBeforePaymentEvents() :void + { + $this->payment_started = true; + + // $data = [ + // 'client_id' => $this->contact->client->id, + // 'date' => now()->format('Y-m-d'), + // 'invitations' => [[ + // 'key' => '', + // 'client_contact_id' => $this->contact->hashed_id, + // ]], + // 'user_input_promo_code' => $this->coupon, + // 'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon, + // // 'quantity' => $this->quantity, + // ]; + + // $is_eligible = $this->subscription->service()->isEligible($this->contact); + + // if (is_array($is_eligible) && $is_eligible['message'] != 'Success') { + // $this->steps['not_eligible'] = true; + // $this->steps['not_eligible_message'] = $is_eligible['message']; + // $this->steps['show_loading_bar'] = false; + + // return; + // } + + // $this->invoice = $this->subscription + // ->service() + // ->createInvoice($data, $this->quantity) + // ->service() + // ->markSent() + // ->fillDefaults() + // ->adjustInventory() + // ->save(); + + // Cache::put($this->hash, [ + // 'subscription_id' => $this->subscription->id, + // 'email' => $this->email ?? $this->contact->email, + // 'client_id' => $this->contact->client->id, + // 'invoice_id' => $this->invoice->id, + // 'context' => 'purchase', + // 'campaign' => $this->campaign, + // ], now()->addMinutes(60)); + + $this->emit('beforePaymentEventsCompleted'); + } + + + + + + + + + + + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public function rules() @@ -597,104 +690,8 @@ class BillingPortalPurchasev2 extends Component return $client->fresh()->contacts->first(); } - /** - * Fetching payment methods from the client. - * - * @param ClientContact $contact - * @return $this - */ - protected function getPaymentMethods(): self - { - - $this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total); - - // if ($this->subscription->trial_enabled) { - // $this->heading_text = ctrans('texts.plan_trial'); - // $this->steps['show_start_trial'] = true; - - // return $this; - // } - - // if ((int)$this->price == 0) - // $this->steps['payment_required'] = false; - // else - // $this->steps['fetched_payment_methods'] = true; - - // $this->methods = $contact->client->service()->getPaymentMethods($this->price); - - // $this->heading_text = ctrans('texts.payment_methods'); - - return $this; - } - - /** - * Middle method between selecting payment method & - * submitting the from to the backend. - * - * @param $company_gateway_id - * @param $gateway_type_id - */ - public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id) - { - $this->company_gateway_id = $company_gateway_id; - $this->payment_method_id = $gateway_type_id; - - $this->handleBeforePaymentEvents(); - } - - /** - * Method to handle events before payments. - * - * @return void - */ - public function handleBeforePaymentEvents() - { - $this->steps['started_payment'] = true; - $this->steps['show_loading_bar'] = true; - - $data = [ - 'client_id' => $this->contact->client->id, - 'date' => now()->format('Y-m-d'), - 'invitations' => [[ - 'key' => '', - 'client_contact_id' => $this->contact->hashed_id, - ]], - 'user_input_promo_code' => $this->coupon, - 'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon, - // 'quantity' => $this->quantity, - ]; - - $is_eligible = $this->subscription->service()->isEligible($this->contact); - - if (is_array($is_eligible) && $is_eligible['message'] != 'Success') { - $this->steps['not_eligible'] = true; - $this->steps['not_eligible_message'] = $is_eligible['message']; - $this->steps['show_loading_bar'] = false; - - return; - } - - $this->invoice = $this->subscription - ->service() - ->createInvoice($data, $this->quantity) - ->service() - ->markSent() - ->fillDefaults() - ->adjustInventory() - ->save(); - - Cache::put($this->hash, [ - 'subscription_id' => $this->subscription->id, - 'email' => $this->email ?? $this->contact->email, - 'client_id' => $this->contact->client->id, - 'invoice_id' => $this->invoice->id, - 'context' => 'purchase', - 'campaign' => $this->campaign, - ], now()->addMinutes(60)); - - $this->emit('beforePaymentEventsCompleted'); - } + /** * Proxy method for starting the trial. * diff --git a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php index 4c78b9ef2b9c..6c03ece2f88f 100644 --- a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php @@ -231,14 +231,14 @@
@if($authenticated) -
+

{{ $heading_text ?? ctrans('texts.checkout') }}

@if (session()->has('message')) @component('portal.ninja2020.components.message') {{ session('message') }} @endcomponent @endif - @if(count($methods) > 0) + @if(count($methods) > 0 && !$payment_started)
@foreach($methods as $method)
@endif