mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
wip
This commit is contained in:
parent
81f5808bf6
commit
127c6cb3cd
47
app/DataMapper/Billing/WebhookConfiguration.php
Normal file
47
app/DataMapper/Billing/WebhookConfiguration.php
Normal 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',
|
||||
];
|
||||
}
|
@ -29,6 +29,7 @@ use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
@ -237,11 +238,18 @@ class PaymentController extends Controller
|
||||
->get();
|
||||
}
|
||||
|
||||
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals];
|
||||
|
||||
if ($request->query('hash')) {
|
||||
$hash_data['billing_context'] = Cache::get($request->query('hash'));
|
||||
}
|
||||
|
||||
$payment_hash = new PaymentHash;
|
||||
$payment_hash->hash = Str::random(128);
|
||||
$payment_hash->data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals];
|
||||
$payment_hash->data = $hash_data;
|
||||
$payment_hash->fee_total = $fee_totals;
|
||||
$payment_hash->fee_invoice_id = $first_invoice->id;
|
||||
|
||||
$payment_hash->save();
|
||||
|
||||
$totals = [
|
||||
|
@ -7,6 +7,7 @@ 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
|
||||
@ -42,6 +43,8 @@ class BillingPortalPurchase extends Component
|
||||
|
||||
public $invoice;
|
||||
|
||||
public $coupon;
|
||||
|
||||
public function authenticate()
|
||||
{
|
||||
$this->validate();
|
||||
@ -88,8 +91,6 @@ class BillingPortalPurchase extends Component
|
||||
|
||||
protected function getPaymentMethods(ClientContact $contact): self
|
||||
{
|
||||
// Cache::put($this->hash, ['email' => $this->email ?? $this->contact->email, 'url' => url()->current()]);
|
||||
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
|
||||
$this->methods = $contact->client->service()->getPaymentMethods(1000);
|
||||
@ -120,7 +121,7 @@ class BillingPortalPurchase extends Component
|
||||
'key' => '',
|
||||
'client_contact_id' => $this->contact->hashed_id,
|
||||
]],
|
||||
'user_input_promo_code' => '', // Field to input the promo code,
|
||||
'user_input_promo_code' => $this->coupon,
|
||||
'quantity' => 1, // Option to increase quantity
|
||||
];
|
||||
|
||||
@ -131,9 +132,21 @@ class BillingPortalPurchase extends Component
|
||||
->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) {
|
||||
|
@ -21,6 +21,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Company extends BaseModel
|
||||
{
|
||||
@ -286,7 +287,7 @@ class Company extends BaseModel
|
||||
*/
|
||||
public function country()
|
||||
{
|
||||
//return $this->belongsTo(Country::class);
|
||||
// return $this->belongsTo(Country::class);
|
||||
return Country::find($this->settings->country_id);
|
||||
}
|
||||
|
||||
@ -342,12 +343,13 @@ class Company extends BaseModel
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
$currencies = Cache::get('currencies');
|
||||
|
||||
return $currencies->filter(function ($item) {
|
||||
return $item->id == $this->settings->currency_id;
|
||||
})->first();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,6 +30,7 @@ use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\Services\BillingSubscription\BillingSubscriptionService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SystemLogTrait;
|
||||
@ -240,6 +241,8 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
BillingSubscriptionService::completePurchase($this->payment_hash);
|
||||
|
||||
return $payment->service()->applyNumber()->save();
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ use App\DataMapper\InvoiceItem;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Models\BillingSubscription;
|
||||
use App\Models\ClientSubscription;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\Product;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
|
||||
@ -107,4 +108,14 @@ class BillingSubscriptionService
|
||||
{
|
||||
//scan for any notification we are required to send
|
||||
}
|
||||
|
||||
public static function completePurchase(PaymentHash $payment_hash)
|
||||
{
|
||||
if (!property_exists($payment_hash, 'billing_context')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point we have some state carried from the billing page
|
||||
// to this, available as $payment_hash->data->billing_context. Make something awesome ⭐
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Currency;
|
||||
|
||||
/**
|
||||
@ -88,12 +89,12 @@ class Number
|
||||
* Formats a given value based on the clients currency AND country.
|
||||
*
|
||||
* @param floatval $value The number to be formatted
|
||||
* @param $client
|
||||
* @param $entity
|
||||
* @return string The formatted value
|
||||
*/
|
||||
public static function formatMoney($value, $client) :string
|
||||
public static function formatMoney($value, $entity) :string
|
||||
{
|
||||
$currency = $client->currency();
|
||||
$currency = $entity->currency();
|
||||
|
||||
$thousand = $currency->thousand_separator;
|
||||
$decimal = $currency->decimal_separator;
|
||||
@ -101,29 +102,38 @@ class Number
|
||||
$code = $currency->code;
|
||||
$swapSymbol = $currency->swap_currency_symbol;
|
||||
|
||||
// App\Models\Client::country() returns instance of BelongsTo.
|
||||
// App\Models\Company::country() returns record for the country, that's why we check for the instance.
|
||||
|
||||
if ($entity instanceof Company) {
|
||||
$country = $entity->country();
|
||||
} else {
|
||||
$country = $entity->country;
|
||||
}
|
||||
|
||||
/* Country settings override client settings */
|
||||
if (isset($client->country->thousand_separator) && strlen($client->country->thousand_separator) >= 1) {
|
||||
$thousand = $client->country->thousand_separator;
|
||||
if (isset($country->thousand_separator) && strlen($country->thousand_separator) >= 1) {
|
||||
$thousand = $country->thousand_separator;
|
||||
}
|
||||
|
||||
if (isset($client->country->decimal_separator) && strlen($client->country->decimal_separator) >= 1) {
|
||||
$decimal = $client->country->decimal_separator;
|
||||
if (isset($country->decimal_separator) && strlen($country->decimal_separator) >= 1) {
|
||||
$decimal = $country->decimal_separator;
|
||||
}
|
||||
|
||||
if (isset($client->country->swap_currency_symbol) && strlen($client->country->swap_currency_symbol) >= 1) {
|
||||
$swapSymbol = $client->country->swap_currency_symbol;
|
||||
if (isset($country->swap_currency_symbol) && strlen($country->swap_currency_symbol) >= 1) {
|
||||
$swapSymbol = $country->swap_currency_symbol;
|
||||
}
|
||||
|
||||
$value = number_format($value, $precision, $decimal, $thousand);
|
||||
$symbol = $currency->symbol;
|
||||
|
||||
if ($client->getSetting('show_currency_code') === true && $currency->code == 'CHF') {
|
||||
if ($entity->getSetting('show_currency_code') === true && $currency->code == 'CHF') {
|
||||
return "{$code} {$value}";
|
||||
} elseif ($client->getSetting('show_currency_code') === true) {
|
||||
} elseif ($entity->getSetting('show_currency_code') === true) {
|
||||
return "{$value} {$code}";
|
||||
} elseif ($swapSymbol) {
|
||||
return "{$value} ".trim($symbol);
|
||||
} elseif ($client->getSetting('show_currency_code') === false) {
|
||||
} elseif ($entity->getSetting('show_currency_code') === false) {
|
||||
return "{$symbol}{$value}";
|
||||
} else {
|
||||
return self::formatValue($value, $currency);
|
||||
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||
"/css/app.css": "/css/app.css?id=8d3e488939aa216c7a1c",
|
||||
"/css/app.css": "/css/app.css?id=e8d6d5e8cb60bc2f15b3",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
||||
|
@ -1,19 +1,21 @@
|
||||
<div class="grid grid-cols-12">
|
||||
<!-- Left side with payment/product information. -->
|
||||
<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">
|
||||
<h1 class="text-3xl font-bold tracking-wide">Summary</h1>
|
||||
<p class="text-gray-800 tracking-wide text-sm">A brief overview of the order</p>
|
||||
<img class="h-8" src="{{ $billing_subscription->company->present()->logo }}"
|
||||
alt="{{ $billing_subscription->company->present()->name }}">
|
||||
|
||||
<p class="my-6">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Culpa earum eos explicabo labore
|
||||
laboriosam numquam officia pariatur recusandae repellat. Aliquam aliquid amet dignissimos facere iste,
|
||||
provident sed voluptas! Consequuntur ea expedita magnam maiores nisi rem saepe suscipit. At autem,
|
||||
expedita explicabo fugiat ipsam maiores modi, odit quae quia quos, voluptatum!</p>
|
||||
<h1 id="billing-page-company-logo" class="text-3xl font-bold tracking-wide mt-8">
|
||||
{{ $billing_subscription->product->product_key }}
|
||||
</h1>
|
||||
|
||||
<span class="text-sm uppercase font-bold">Total:</span>
|
||||
<h1 class="text-2xl font-bold tracking-wide">$4,000</h1>
|
||||
<p class="my-6">{{ $billing_subscription->product->notes }}</p>
|
||||
|
||||
<a href="#" class="block mt-16 inline-flex items-center space-x-2">
|
||||
<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">
|
||||
@ -21,8 +23,9 @@
|
||||
<polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
|
||||
<span>Go back</span>
|
||||
<span>{{ ctrans('texts.client_portal') }}</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -59,7 +62,7 @@
|
||||
@foreach($this->methods as $method)
|
||||
<button
|
||||
wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}')"
|
||||
class="p-4 border rounded mr-4 hover:border-blue-600">
|
||||
class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
|
||||
{{ $method['label'] }}
|
||||
</button>
|
||||
@endforeach
|
||||
@ -69,7 +72,7 @@
|
||||
@csrf
|
||||
|
||||
<label for="email_address">
|
||||
<span class="input-label">E-mail address</span>
|
||||
<span class="input-label">{{ ctrans('texts.email_address') }}</span>
|
||||
<input wire:model.defer="email" type="email" class="input w-full"/>
|
||||
|
||||
@error('email')
|
||||
@ -81,7 +84,7 @@
|
||||
|
||||
@if($steps['existing_user'])
|
||||
<label for="password" class="block mt-2">
|
||||
<span class="input-label">Password</span>
|
||||
<span class="input-label">{{ ctrans('texts.password') }}</span>
|
||||
<input wire:model.defer="password" type="password" class="input w-full" autofocus/>
|
||||
|
||||
@error('password')
|
||||
@ -92,9 +95,32 @@
|
||||
</label>
|
||||
@endif
|
||||
|
||||
<button type="submit" class="button button-block bg-primary text-white mt-4">Next</button>
|
||||
<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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user