This commit is contained in:
David Bomba 2021-03-18 11:58:10 +11:00
parent 5041e11933
commit ee15ea9434
5 changed files with 379 additions and 0 deletions

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\DataMapper\Billing;
class WebhookConfiguration
{
/**
* @var string
*/
public $return_url = '';
/**
* @var string
*/
public $post_purchase_url = '';
/**
* @var array
*/
public $post_purchase_headers = [];
/**
* @var string
*/
public $post_purchase_body = '';
/**
* @var array
*/
public static $casts = [
'return_url' => 'string',
'post_purchase_url' => 'string',
'post_purchase_headers' => 'array',
'post_purchase_body' => 'object',
];
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers\ClientPortal;
use App\Http\Controllers\Controller;
use App\Models\BillingSubscription;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class BillingSubscriptionPurchaseController extends Controller
{
public function index(BillingSubscription $billing_subscription)
{
return view('billing-portal.purchase', [
'billing_subscription' => $billing_subscription,
'hash' => Str::uuid()->toString(),
]);
}
}

View File

@ -0,0 +1,158 @@
<?php
namespace App\Http\Livewire;
use App\Factory\ClientFactory;
use App\Models\ClientContact;
use App\Repositories\ClientContactRepository;
use App\Repositories\ClientRepository;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class BillingPortalPurchase extends Component
{
public $hash;
public $heading_text = 'Log in';
public $email;
public $password;
public $billing_subscription;
public $contact;
protected $rules = [
'email' => ['required', 'email'],
];
public $company_gateway_id;
public $payment_method_id;
public $steps = [
'passed_email' => false,
'existing_user' => false,
'fetched_payment_methods' => false,
'fetched_client' => false,
];
public $methods = [];
public $invoice;
public $coupon;
public function authenticate()
{
$this->validate();
$contact = ClientContact::where('email', $this->email)->first();
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]);
return $attempt
? $this->getPaymentMethods($contact)
: session()->flash('message', 'These credentials do not match our records.');
}
$this->steps['existing_user'] = false;
$contact = $this->createBlankClient();
if ($contact && $contact instanceof ClientContact) {
$this->getPaymentMethods($contact);
}
}
protected function createBlankClient()
{
$company = $this->billing_subscription->company;
$user = $this->billing_subscription->user;
$client_repo = new ClientRepository(new ClientContactRepository());
$client = $client_repo->save([
'name' => 'Client Name',
'contacts' => [
['email' => $this->email],
]
], ClientFactory::create($company->id, $user->id));
return $client->contacts->first();
}
protected function getPaymentMethods(ClientContact $contact): self
{
$this->steps['fetched_payment_methods'] = true;
$this->methods = $contact->client->service()->getPaymentMethods(1000);
$this->heading_text = 'Pick a payment method';
Auth::guard('contact')->login($contact);
$this->contact = $contact;
return $this;
}
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();
}
public function handleBeforePaymentEvents()
{
$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,
'quantity' => 1, // Option to increase quantity
];
$this->invoice = $this->billing_subscription
->service()
->createInvoice($data)
->service()
->markSent()
->save();
Cache::put($this->hash, [
'email' => $this->email ?? $this->contact->email,
'client_id' => $this->contact->client->id,
'invoice_id' => $this->invoice->id],
now()->addMinutes(60)
);
$this->emit('beforePaymentEventsCompleted');
}
public function applyCouponCode()
{
dd('Applying coupon code: ' . $this->coupon);
}
public function render()
{
if ($this->contact instanceof ClientContact) {
$this->getPaymentMethods($this->contact);
}
return render('components.livewire.billing-portal-purchase');
}
}

View File

@ -0,0 +1,17 @@
@extends('portal.ninja2020.layout.clean')
@section('meta_title', $billing_subscription->product->product_key)
@section('body')
@livewire('billing-portal-purchase', ['billing_subscription' => $billing_subscription, 'contact' => auth('contact')->user(), 'hash' => $hash])
@stop
@push('footer')
<script>
function updateGatewayFields(companyGatewayId, paymentMethodId) {
document.getElementById('company_gateway_id').value = companyGatewayId;
document.getElementById('payment_method_id').value = paymentMethodId;
}
Livewire.on('beforePaymentEventsCompleted', () => document.getElementById('payment-method-form').submit());
</script>
@endpush

View File

@ -0,0 +1,127 @@
<div class="grid grid-cols-12">
<div class="col-span-12 lg:col-span-6 bg-gray-50 shadow-lg lg:h-screen flex flex-col items-center">
<div class="w-full p-10 lg:w-1/2 lg:mt-48 lg:p-0">
<img class="h-8" src="{{ $billing_subscription->company->present()->logo }}"
alt="{{ $billing_subscription->company->present()->name }}">
<h1 id="billing-page-company-logo" class="text-3xl font-bold tracking-wide mt-8">
{{ $billing_subscription->product->product_key }}
</h1>
<p class="my-6">{{ $billing_subscription->product->notes }}</p>
<span class="text-sm uppercase font-bold">{{ ctrans('texts.total') }}:</span>
<h1 class="text-2xl font-bold tracking-wide">{{ App\Utils\Number::formatMoney($billing_subscription->product->price, $billing_subscription->company) }}</h1>
@if(auth('contact')->user())
<a href="{{ route('client.invoices.index') }}" class="block mt-16 inline-flex items-center space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-arrow-left">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
<span>{{ ctrans('texts.client_portal') }}</span>
</a>
@endif
</div>
</div>
<div class="col-span-12 lg:col-span-6 bg-white lg:shadow-lg lg:h-screen">
<div class="grid grid-cols-12 flex flex-col p-10 lg:mt-48 lg:ml-16">
<div class="col-span-12 w-full lg:col-span-6">
<h2 class="text-2xl font-bold tracking-wide">{{ $heading_text }}</h2>
@if (session()->has('message'))
@component('portal.ninja2020.components.message')
{{ session('message') }}
@endcomponent
@endif
@if($this->steps['fetched_payment_methods'])
<div class="flex items-center mt-4 text-sm">
<form action="{{ route('client.payments.process', ['hash' => $hash, 'sidebar' => 'hidden']) }}"
method="post"
id="payment-method-form">
@csrf
@if($invoice instanceof \App\Models\Invoice)
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
<input type="hidden" name="payable_invoices[0][amount]"
value="{{ $invoice->partial > 0 ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency()) }}">
<input type="hidden" name="payable_invoices[0][invoice_id]"
value="{{ $invoice->hashed_id }}">
@endif
<input type="hidden" name="action" value="payment">
<input type="hidden" name="company_gateway_id" value="{{ $company_gateway_id }}"/>
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}"/>
</form>
@foreach($this->methods as $method)
<button
wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}')"
class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
{{ $method['label'] }}
</button>
@endforeach
</div>
@else
<form wire:submit.prevent="authenticate" class="mt-8">
@csrf
<label for="email_address">
<span class="input-label">{{ ctrans('texts.email_address') }}</span>
<input wire:model.defer="email" type="email" class="input w-full"/>
@error('email')
<p class="validation validation-fail block w-full" role="alert">
{{ $message }}
</p>
@enderror
</label>
@if($steps['existing_user'])
<label for="password" class="block mt-2">
<span class="input-label">{{ ctrans('texts.password') }}</span>
<input wire:model.defer="password" type="password" class="input w-full" autofocus/>
@error('password')
<p class="validation validation-fail block w-full" role="alert">
{{ $message }}
</p>
@enderror
</label>
@endif
<button type="submit"
class="button button-block bg-primary text-white mt-4">{{ ctrans('texts.next') }}</button>
</form>
@endif
<div class="relative mt-8">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm leading-5">
<span class="px-2 text-gray-700 bg-white">Have a coupon code?</span>
</div>
</div>
<form wire:submit.prevent="applyCouponCode" class="mt-4">
@csrf
<div class="flex items-center">
<label class="w-full mr-2">
<input type="text" wire:model.defer="coupon" class="input w-full m-0" />
</label>
<button class="button bg-primary m-0 text-white">{{ ctrans('texts.apply') }}</button>
</div>
</form>
</div>
</div>
</div>
</div>