Client portal improvements & bug fixes (#3678)

* fix turbolink 404

* fix checkbox when adding card using stripe

* Fix hashed_ids problem when using PayPal

* php-cs-fixer

* Bump tailwind & purgecss

* fix auth scope

* scope to auth contact company
This commit is contained in:
Benjamin Beganović 2020-05-09 00:20:37 +02:00 committed by GitHub
parent 7f9abbf96b
commit 2704bf2f82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 47 additions and 55 deletions

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Invoice Ninja (https://invoiceninja.com) * Invoice Ninja (https://invoiceninja.com)
* *
@ -71,8 +72,8 @@ class PaymentController extends Controller
public function process() public function process()
{ {
$invoices = Invoice::whereIn('id', $this->transformKeys(request()->invoices)) $invoices = Invoice::whereIn('id', $this->transformKeys(request()->invoices))
->whereClientId(auth()->user()->client->id) ->whereClientId(auth('contact')->user()->company()->id)
->get(); ->get();
$amount = $invoices->sum('balance'); $amount = $invoices->sum('balance');
@ -92,16 +93,12 @@ class PaymentController extends Controller
return $invoice; return $invoice;
}); });
$payment_methods = auth()->user()->client->getPaymentMethods($amount); $payment_methods = auth()->user()->client->getPaymentMethods($amount);
//boot the payment gateway
$gateway = CompanyGateway::find(request()->input('company_gateway_id')); $gateway = CompanyGateway::find(request()->input('company_gateway_id'));
$payment_method_id = request()->input('payment_method_id'); $payment_method_id = request()->input('payment_method_id');
//if there is a gateway fee, now is the time to calculate it // Place to calculate gateway fee.
//and add it to the invoice
$data = [ $data = [
'invoices' => $invoices, 'invoices' => $invoices,
@ -110,7 +107,7 @@ class PaymentController extends Controller
'amount_with_fee' => $amount + $gateway->calcGatewayFee($amount), 'amount_with_fee' => $amount + $gateway->calcGatewayFee($amount),
'token' => auth()->user()->client->gateway_token($gateway->id, $payment_method_id), 'token' => auth()->user()->client->gateway_token($gateway->id, $payment_method_id),
'payment_method_id' => $payment_method_id, 'payment_method_id' => $payment_method_id,
'hashed_ids' => explode(",", request()->input('hashed_ids')), 'hashed_ids' => request()->invoices,
]; ];

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Invoice Ninja (https://invoiceninja.com) * Invoice Ninja (https://invoiceninja.com)
* *
@ -18,9 +19,9 @@ use App\Models\CompanyGateway;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SystemLogTrait; use App\Utils\Traits\SystemLogTrait;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Omnipay\Omnipay; use Omnipay\Omnipay;
/** /**
@ -44,6 +45,7 @@ use Omnipay\Omnipay;
class BasePaymentDriver class BasePaymentDriver
{ {
use SystemLogTrait; use SystemLogTrait;
use MakesHash;
/* The company gateway instance*/ /* The company gateway instance*/
protected $company_gateway; protected $company_gateway;
@ -105,7 +107,7 @@ class BasePaymentDriver
]; ];
} }
public function getCompanyGatewayId() :int public function getCompanyGatewayId(): int
{ {
return $this->company_gateway->id; return $this->company_gateway->id;
} }
@ -113,7 +115,7 @@ class BasePaymentDriver
* Returns whether refunds are possible with the gateway * Returns whether refunds are possible with the gateway
* @return boolean TRUE|FALSE * @return boolean TRUE|FALSE
*/ */
public function getRefundable() :bool public function getRefundable(): bool
{ {
return $this->refundable; return $this->refundable;
} }
@ -122,7 +124,7 @@ class BasePaymentDriver
* Returns whether token billing is possible with the gateway * Returns whether token billing is possible with the gateway
* @return boolean TRUE|FALSE * @return boolean TRUE|FALSE
*/ */
public function getTokenBilling() :bool public function getTokenBilling(): bool
{ {
return $this->token_billing; return $this->token_billing;
} }
@ -132,7 +134,7 @@ class BasePaymentDriver
* authorise and credit card. * authorise and credit card.
* @return [type] [description] * @return [type] [description]
*/ */
public function canAuthoriseCreditCard() :bool public function canAuthoriseCreditCard(): bool
{ {
return $this->can_authorise_credit_card; return $this->can_authorise_credit_card;
} }
@ -149,11 +151,7 @@ class BasePaymentDriver
$amount = $payment->getCompletedAmount(); $amount = $payment->getCompletedAmount();
} }
if ($payment->is_deleted) { if ($payment->is_deleted || !$amount) {
return false;
}
if (! $amount) {
return false; return false;
} }
@ -224,9 +222,9 @@ class BasePaymentDriver
refund($options) - refund an already processed transaction refund($options) - refund an already processed transaction
void($options) - generally can only be called up to 24 hours after submitting a transaction void($options) - generally can only be called up to 24 hours after submitting a transaction
acceptNotification() - convert an incoming request from an off-site gateway to a generic notification object for further processing acceptNotification() - convert an incoming request from an off-site gateway to a generic notification object for further processing
*/ */
protected function paymentDetails($input) : array protected function paymentDetails($input): array
{ {
$data = [ $data = [
'currency' => $this->client->getCurrencyCode(), 'currency' => $this->client->getCurrencyCode(),
@ -242,10 +240,10 @@ class BasePaymentDriver
{ {
$this->gateway(); $this->gateway();
$response = $this->gateway $response = $this->gateway
->purchase($data) ->purchase($data)
->setItems($items) ->setItems($items)
->send(); ->send();
return $response; return $response;
/* /*
@ -257,11 +255,11 @@ class BasePaymentDriver
$this->gateway(); $this->gateway();
return $this->gateway return $this->gateway
->completePurchase($data) ->completePurchase($data)
->send(); ->send();
} }
public function createPayment($data) : Payment public function createPayment($data): Payment
{ {
$payment = PaymentFactory::create($this->client->company->id, $this->client->user->id); $payment = PaymentFactory::create($this->client->company->id, $this->client->user->id);
$payment->client_id = $this->client->id; $payment->client_id = $this->client->id;
@ -274,11 +272,14 @@ class BasePaymentDriver
} }
public function attachInvoices(Payment $payment, $hashed_ids) : Payment public function attachInvoices(Payment $payment, $hashed_ids): Payment
{ {
$invoices = Invoice::whereIn('id', $this->transformKeys($hashed_ids)) $transformed = $this->transformKeys($hashed_ids);
->whereClientId($this->client->id) $array = is_array($transformed) ? $transformed : [$transformed];
->get();
$invoices = Invoice::whereIn('id', $array)
->whereClientId($this->client->id)
->get();
$payment->invoices()->sync($invoices); $payment->invoices()->sync($invoices);
$payment->save(); $payment->save();

View File

@ -25,7 +25,7 @@
"resolve-url-loader": "^3.1.0", "resolve-url-loader": "^3.1.0",
"sass": "^1.15.2", "sass": "^1.15.2",
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
"tailwindcss": "^1.3", "tailwindcss": "^1.4",
"turbolinks": "^5.2.0" "turbolinks": "^5.2.0"
} }
} }

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
/*! For license information please see action-selectors.js.LICENSE.txt */ /*! For license information please see action-selectors.js.LICENSE.txt */
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=2)}({2:function(e,t,n){e.exports=n("Boob")},Boob:function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.parentElement=document.querySelector(".form-check-parent"),this.parentForm=document.getElementById("bulkActions")}var t,r,o;return t=e,(r=[{key:"watchCheckboxes",value:function(e){var t=this;document.querySelectorAll(".form-check-child").forEach((function(n){e.checked?(n.checked=e.checked,t.processChildItem(n,document.getElementById("bulkActions"))):(n.checked=!1,document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()})))}))}},{key:"processChildItem",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};n.hasOwnProperty("single")&&document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()}));var r=document.createElement("INPUT");r.setAttribute("name","invoices[]"),r.setAttribute("value",e.dataset.value),r.setAttribute("class","child-hidden-input"),r.hidden=!0,t.append(r)}},{key:"handle",value:function(){var e=this;this.parentElement.addEventListener("click",(function(){e.watchCheckboxes(e.parentElement)}));var t=!0,n=!1,r=void 0;try{for(var o,c=function(){var t=o.value;t.addEventListener("click",(function(){e.processChildItem(t,e.parentForm,{single:!0}),document.querySelector('button[value="payment"]').click()}))},u=document.querySelectorAll(".pay-now-button")[Symbol.iterator]();!(t=(o=u.next()).done);t=!0)c()}catch(e){n=!0,r=e}finally{try{t||null==u.return||u.return()}finally{if(n)throw r}}var l=!0,i=!1,a=void 0;try{for(var d,f=function(){var t=d.value;t.addEventListener("click",(function(){e.processChildItem(t,e.parentForm)}))},s=document.querySelectorAll(".form-check-child")[Symbol.iterator]();!(l=(d=s.next()).done);l=!0)f()}catch(e){i=!0,a=e}finally{try{l||null==s.return||s.return()}finally{if(i)throw a}}}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()}}); !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=2)}({2:function(e,t,n){e.exports=n("Boob")},Boob:function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.parentElement=document.querySelector(".form-check-parent"),this.parentForm=document.getElementById("bulkActions")}var t,r,o;return t=e,(r=[{key:"watchCheckboxes",value:function(e){var t=this;document.querySelectorAll(".form-check-child").forEach((function(n){e.checked?(n.checked=e.checked,t.processChildItem(n,document.getElementById("bulkActions"))):(n.checked=!1,document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()})))}))}},{key:"processChildItem",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};n.hasOwnProperty("single")&&document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()}));var r=document.createElement("INPUT");r.setAttribute("name","invoices[]"),r.setAttribute("value",e.dataset.value),r.setAttribute("class","child-hidden-input"),r.hidden=!0,t.append(r)}},{key:"handle",value:function(){var e=this;this.parentElement.addEventListener("click",(function(){e.watchCheckboxes(e.parentElement)}));var t=!0,n=!1,r=void 0;try{for(var o,c=function(){var t=o.value;t.addEventListener("click",(function(){e.processChildItem(t,e.parentForm)}))},u=document.querySelectorAll(".form-check-child")[Symbol.iterator]();!(t=(o=u.next()).done);t=!0)c()}catch(e){n=!0,r=e}finally{try{t||null==u.return||u.return()}finally{if(n)throw r}}}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()}});

View File

@ -1,7 +1,7 @@
{ {
"/js/app.js": "/js/app.js?id=49b0ee243139f6e72bd9", "/js/app.js": "/js/app.js?id=49b0ee243139f6e72bd9",
"/css/app.css": "/css/app.css?id=c317169666198ce30427", "/css/app.css": "/css/app.css?id=9b6694588d0604760071",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=caec43815d9a13168a38", "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=3e58537817b968346c9f",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=af49e24958be5fc00c92", "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=af49e24958be5fc00c92",
"/js/clients/payment_methods/authorize-stripe-card.js": "/js/clients/payment_methods/authorize-stripe-card.js?id=f4c45f0da9868d840799", "/js/clients/payment_methods/authorize-stripe-card.js": "/js/clients/payment_methods/authorize-stripe-card.js?id=f4c45f0da9868d840799",
"/js/clients/payments/process.js": "/js/clients/payments/process.js?id=bb91cb611d1bdab40973", "/js/clients/payments/process.js": "/js/clients/payments/process.js?id=bb91cb611d1bdab40973",

View File

@ -50,13 +50,6 @@ class ActionSelectors {
this.watchCheckboxes(this.parentElement); this.watchCheckboxes(this.parentElement);
}); });
for (let child of document.querySelectorAll(".pay-now-button")) {
child.addEventListener("click", () => {
this.processChildItem(child, this.parentForm, { single: true });
document.querySelector('button[value="payment"]').click();
});
}
for (let child of document.querySelectorAll(".form-check-child")) { for (let child of document.querySelectorAll(".form-check-child")) {
child.addEventListener("click", () => { child.addEventListener("click", () => {
this.processChildItem(child, this.parentForm); this.processChildItem(child, this.parentForm);

View File

@ -87,9 +87,14 @@
</td> </td>
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium"> <td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
@if($invoice->isPayable()) @if($invoice->isPayable())
<button class="button button-primary py-1 px-2 text-xs uppercase mr-3 pay-now-button" data-value="{{ $invoice->hashed_id }}"> <form action="{{ route('client.invoices.bulk') }}" method="post">
@lang('texts.pay_now') @csrf
</button> <input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
<input type="hidden" name="action" value="payment">
<button class="button button-primary py-1 px-2 text-xs uppercase mr-3">
@lang('texts.pay_now')
</button>
</form>
@endif @endif
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}" class="button-link"> <a href="{{ route('client.invoice.show', $invoice->hashed_id) }}" class="button-link">
@lang('texts.view') @lang('texts.view')

View File

@ -80,7 +80,7 @@
{{ ctrans('texts.token_billing_checkbox') }} {{ ctrans('texts.token_billing_checkbox') }}
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input type="checkbox" class="form-check" name="token-billing-checkbox"/> <input type="checkbox" class="form-checkbox" name="token-billing-checkbox"/>
</dd> </dd>
</div> </div>
<div class="bg-white px-4 py-5 flex justify-end"> <div class="bg-white px-4 py-5 flex justify-end">

View File

@ -41,7 +41,7 @@
<div class="rounded-md bg-white shadow-xs"> <div class="rounded-md bg-white shadow-xs">
<div class="py-1"> <div class="py-1">
@foreach($payment_methods as $payment_method) @foreach($payment_methods as $payment_method)
<a href="#" @click="{ open = false }" <a data-turbolinks="false" href="#" @click="{ open = false }"
data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}"
data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}"
class="dropdown-gateway-button block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"> class="dropdown-gateway-button block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">

3
tailwind.config.js vendored
View File

@ -1,6 +1,9 @@
const defaultTheme = require("tailwindcss/defaultTheme"); const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = { module.exports = {
purge: [
'./resources/views/portal/ninja2020/**/*.blade.php',
],
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {

7
webpack.mix.js vendored
View File

@ -1,8 +1,6 @@
const mix = require("laravel-mix"); const mix = require("laravel-mix");
const tailwindcss = require("tailwindcss"); const tailwindcss = require("tailwindcss");
require("laravel-mix-purgecss");
mix.js("resources/js/app.js", "public/js") mix.js("resources/js/app.js", "public/js")
.js( .js(
"resources/js/clients/payment_methods/authorize-stripe-card.js", "resources/js/clients/payment_methods/authorize-stripe-card.js",
@ -41,11 +39,6 @@ mix.sass("resources/sass/app.scss", "public/css")
.options({ .options({
processCssUrls: false, processCssUrls: false,
postCss: [tailwindcss("./tailwind.config.js")] postCss: [tailwindcss("./tailwind.config.js")]
})
.purgeCss({
enabled: mix.inProduction(),
extensions: ["html", "php"]
}); });
mix.version(); mix.version();
mix.disableNotifications(); mix.disableNotifications();