mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Merge remote-tracking branch 'upstream/v2' into v2-pdfmaker-design-improvements
This commit is contained in:
commit
1a66f1835f
@ -1,5 +1,5 @@
|
||||
APP_NAME="Invoice Ninja"
|
||||
APP_ENV=local
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
|
||||
|
@ -238,8 +238,13 @@ class CompanySettings extends BaseSettings
|
||||
public $client_portal_terms = '';
|
||||
public $client_portal_privacy_policy = '';
|
||||
public $client_portal_enable_uploads = false;
|
||||
public $client_portal_allow_under_payment = false;
|
||||
public $client_portal_allow_over_payment = false;
|
||||
|
||||
|
||||
public static $casts = [
|
||||
'client_portal_allow_under_payment' => 'bool',
|
||||
'client_portal_allow_over_payment' => 'bool',
|
||||
'auto_bill' => 'string',
|
||||
'lock_invoices' => 'string',
|
||||
'client_portal_terms' => 'string',
|
||||
|
@ -96,7 +96,8 @@ class InvoiceController extends Controller
|
||||
}
|
||||
|
||||
$invoices->map(function ($invoice) {
|
||||
$invoice->balance = Number::formatMoney($invoice->balance, $invoice->client);
|
||||
$invoice->balance = Number::formatValue($invoice->balance, $invoice->client->currency());
|
||||
$invoice->partial = Number::formatValue($invoice->partial, $invoice->client->currency());
|
||||
return $invoice;
|
||||
});
|
||||
|
||||
@ -113,6 +114,8 @@ class InvoiceController extends Controller
|
||||
'total' => $total,
|
||||
];
|
||||
|
||||
//REFACTOR entry point for online payments starts here
|
||||
|
||||
return $this->render('invoices.payment', $data);
|
||||
}
|
||||
|
||||
|
@ -14,16 +14,19 @@ namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Filters\PaymentFilters;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Jobs\Invoice\InjectSignature;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Cache;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Yajra\DataTables\Facades\DataTables;
|
||||
|
||||
/**
|
||||
@ -72,28 +75,56 @@ class PaymentController extends Controller
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(request()->invoices))
|
||||
->where('company_id', auth('contact')->user()->company->id)
|
||||
->get();
|
||||
//REFACTOR - Here the request will contain an array of invoices and the amount to be charged for the invoice
|
||||
//REFACTOR - At this point, we will also need to modify the invoice to include a line item for a gateway fee if applicable
|
||||
// This is tagged with a type_id of 3 which is for a pending gateway fee.
|
||||
//REFACTOR - In order to preserve state we should save the array of invoices and amounts and store it in db/cache and use a HASH
|
||||
// to rehydrate these values in the payment response.
|
||||
// dd(request()->all());
|
||||
|
||||
$amount = $invoices->sum('balance');
|
||||
$gateway = CompanyGateway::find(request()->input('company_gateway_id'));
|
||||
/*find invoices*/
|
||||
$payable_invoices = request()->payable_invoices;
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payable_invoices, 'invoice_id')))->get();
|
||||
|
||||
/*filter only payable invoices*/
|
||||
$invoices = $invoices->filter(function ($invoice) {
|
||||
return $invoice->isPayable();
|
||||
});
|
||||
|
||||
|
||||
/*return early if no invoices*/
|
||||
if ($invoices->count() == 0) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with(['warning' => 'No payable invoices selected.']);
|
||||
}
|
||||
|
||||
$invoices->map(function ($invoice) {
|
||||
$invoice->balance = Number::formatMoney($invoice->balance, $invoice->client);
|
||||
$invoice->due_date = $this->formatDate($invoice->due_date, $invoice->client->date_format());
|
||||
/*iterate through invoices and add gateway fees and other payment metadata*/
|
||||
|
||||
foreach($payable_invoices as $key => $payable_invoice)
|
||||
{
|
||||
|
||||
$payable_invoices[$key]['amount'] = Number::parseFloat($payable_invoice['amount']);
|
||||
$payable_invoice['amount'] = $payable_invoices[$key]['amount'];
|
||||
|
||||
$invoice = $invoices->first(function ($inv) use($payable_invoice) {
|
||||
return $payable_invoice['invoice_id'] == $inv->hashed_id;
|
||||
});
|
||||
|
||||
return $invoice;
|
||||
});
|
||||
$payable_invoices[$key]['due_date'] = $this->formatDate($invoice->due_date, $invoice->client->date_format());
|
||||
$payable_invoices[$key]['invoice_number'] = $invoice->number;
|
||||
|
||||
if(isset($invoice->po_number))
|
||||
$additional_info = $invoice->po_number;
|
||||
elseif(isset($invoice->public_notes))
|
||||
$additional_info = $invoice->public_notes;
|
||||
else
|
||||
$additional_info = $invoice->date;
|
||||
|
||||
$payable_invoices[$key]['additional_info'] = $additional_info;
|
||||
|
||||
}
|
||||
|
||||
if ((bool) request()->signature) {
|
||||
$invoices->each(function ($invoice) {
|
||||
@ -101,20 +132,46 @@ class PaymentController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
$payment_methods = auth()->user()->client->getPaymentMethods($amount);
|
||||
$gateway = CompanyGateway::find(request()->input('company_gateway_id'));
|
||||
$payment_methods = auth()->user()->client->getPaymentMethods(array_sum(array_column($payable_invoices, 'amount_with_fee')));
|
||||
$payment_method_id = request()->input('payment_method_id');
|
||||
|
||||
// Place to calculate gateway fee.
|
||||
$invoice_totals = array_sum(array_column($payable_invoices,'amount'));
|
||||
|
||||
$first_invoice = $invoices->first();
|
||||
$fee_totals = round($gateway->calcGatewayFee($invoice_totals, true), $first_invoice->client->currency()->precision);
|
||||
|
||||
if(!$first_invoice->uses_inclusive_taxes) {
|
||||
|
||||
$fee_tax = 0;
|
||||
$fee_tax += round(($first_invoice->tax_rate1/100)*$fee_totals, $first_invoice->client->currency()->precision);
|
||||
$fee_tax += round(($first_invoice->tax_rate2/100)*$fee_totals, $first_invoice->client->currency()->precision);
|
||||
$fee_tax += round(($first_invoice->tax_rate3/100)*$fee_totals, $first_invoice->client->currency()->precision);
|
||||
|
||||
$fee_totals += $fee_tax;
|
||||
}
|
||||
|
||||
$first_invoice->service()->addGatewayFee($gateway, $invoice_totals)->save();
|
||||
|
||||
$payment_hash = new PaymentHash;
|
||||
$payment_hash->hash = Str::random(128);
|
||||
$payment_hash->data = $payable_invoices;
|
||||
$payment_hash->fee_total = $fee_totals;
|
||||
$payment_hash->fee_invoice_id = $first_invoice->id;
|
||||
$payment_hash->save();
|
||||
|
||||
$totals = [
|
||||
'invoice_totals' => $invoice_totals,
|
||||
'fee_total' => $fee_totals,
|
||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||
];
|
||||
|
||||
$data = [
|
||||
'invoices' => $invoices,
|
||||
'amount' => $amount,
|
||||
'fee' => $gateway->calcGatewayFee($amount),
|
||||
'amount_with_fee' => $amount + $gateway->calcGatewayFee($amount),
|
||||
'payment_hash' => $payment_hash->hash,
|
||||
'total' => $totals,
|
||||
'invoices' => $payable_invoices,
|
||||
'token' => auth()->user()->client->gateway_token($gateway->id, $payment_method_id),
|
||||
'payment_method_id' => $payment_method_id,
|
||||
'hashed_ids' => request()->invoices,
|
||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||
];
|
||||
|
||||
return $gateway
|
||||
@ -123,10 +180,26 @@ class PaymentController extends Controller
|
||||
->processPaymentView($data);
|
||||
}
|
||||
|
||||
public function response(Request $request)
|
||||
public function response(PaymentResponseRequest $request)
|
||||
{
|
||||
$gateway = CompanyGateway::find($request->input('company_gateway_id'));
|
||||
/*Payment Gateway*/
|
||||
$gateway = CompanyGateway::find($request->input('company_gateway_id'))->firstOrFail();
|
||||
|
||||
//REFACTOR - Entry point for the gateway response - we don't need to do anything at this point.
|
||||
//
|
||||
// - Inside each gateway driver, we should use have a generic code path (in BaseDriver.php)for successful/failed payment
|
||||
//
|
||||
// Success workflow
|
||||
//
|
||||
// - Rehydrate the hash and iterate through the invoices and update the balances
|
||||
// - Update the type_id of the gateway fee to type_id 4
|
||||
// - Link invoices to payment
|
||||
//
|
||||
// Failure workflow
|
||||
//
|
||||
// - Rehydrate hash, iterate through invoices and remove type_id 3's
|
||||
// - Recalcuate invoice totals
|
||||
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod($request->input('payment_method_id'))
|
||||
|
@ -717,6 +717,10 @@ class InvoiceController extends BaseController
|
||||
else
|
||||
$this->reminder_template = $invoice->calculateTemplate();
|
||||
|
||||
//touch reminder1,2,3_sent + last_sent here if the email is a reminder.
|
||||
|
||||
$invoice->service()->touchReminder($this->reminder_template)->save();
|
||||
|
||||
$invoice->invitations->load('contact.client.country','invoice.client.country','invoice.company')->each(function ($invitation) use ($invoice) {
|
||||
|
||||
$email_builder = (new InvoiceEmail())->build($invitation, $this->reminder_template);
|
||||
|
@ -31,7 +31,6 @@ class MigrationController extends BaseController
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Purge Company
|
||||
|
@ -20,35 +20,37 @@ class InvoicesTable extends Component
|
||||
|
||||
public $status = [];
|
||||
|
||||
public function statusChange($status)
|
||||
{
|
||||
if (in_array($status, $this->status)) {
|
||||
return $this->status = array_diff($this->status, [$status]);
|
||||
}
|
||||
|
||||
array_push($this->status, $status);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$local_status = [];
|
||||
|
||||
$query = Invoice::query()
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc');
|
||||
|
||||
if (in_array('paid', $this->status)) {
|
||||
$query = $query->orWhere('status_id', Invoice::STATUS_PAID);
|
||||
$local_status[] = Invoice::STATUS_PAID;
|
||||
}
|
||||
|
||||
if (in_array('unpaid', $this->status)) {
|
||||
$query = $query->orWhereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]);
|
||||
$local_status[] = Invoice::STATUS_SENT;
|
||||
$local_status[] = Invoice::STATUS_PARTIAL;
|
||||
}
|
||||
|
||||
if (in_array('overdue', $this->status)) {
|
||||
$query = $query->orWhereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where(function ($query) {
|
||||
$query
|
||||
->orWhere('due_date', '<', Carbon::now())
|
||||
->orWhere('partial_due_date', '<', Carbon::now());
|
||||
});
|
||||
$local_status[] = Invoice::STATUS_SENT;
|
||||
$local_status[] = Invoice::STATUS_PARTIAL;
|
||||
}
|
||||
|
||||
if (count($local_status) > 0) {
|
||||
$query = $query->whereIn('status_id', array_unique($local_status));
|
||||
}
|
||||
|
||||
if (in_array('overdue', $this->status)) {
|
||||
$query = $query->where(function ($query) {
|
||||
$query
|
||||
->orWhere('due_date', '<', Carbon::now())
|
||||
->orWhere('partial_due_date', '<', Carbon::now());
|
||||
});
|
||||
}
|
||||
|
||||
$query = $query
|
||||
@ -56,6 +58,27 @@ class InvoicesTable extends Component
|
||||
->where('status_id', '<>', Invoice::STATUS_DRAFT)
|
||||
->paginate($this->per_page);
|
||||
|
||||
if (in_array('gateway_fees', $this->status)) {
|
||||
$transformed = $query
|
||||
->getCollection()
|
||||
->filter(function ($invoice) {
|
||||
$invoice['line_items'] = collect($invoice->line_items)
|
||||
->filter(function ($item) {
|
||||
return $item->type_id == "4" || $item->type_id == 4;
|
||||
});
|
||||
|
||||
return count($invoice['line_items']);
|
||||
});
|
||||
|
||||
$query = new \Illuminate\Pagination\LengthAwarePaginator(
|
||||
$transformed,
|
||||
$transformed->count(),
|
||||
$query->perPage(),
|
||||
$query->currentPage(),
|
||||
['path' => request()->url(), 'query' => ['page' => $query->currentPage()]]
|
||||
);
|
||||
}
|
||||
|
||||
return render('components.livewire.invoices-table', [
|
||||
'invoices' => $query,
|
||||
]);
|
||||
|
@ -4,6 +4,7 @@ namespace App\Http\Livewire;
|
||||
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Traits\WithSorting;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
@ -15,34 +16,13 @@ class QuotesTable extends Component
|
||||
public $per_page = 10;
|
||||
public $status = [];
|
||||
|
||||
public function statusChange($status)
|
||||
{
|
||||
if (in_array($status, $this->status)) {
|
||||
return $this->status = array_diff($this->status, [$status]);
|
||||
}
|
||||
|
||||
array_push($this->status, $status);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = Quote::query()
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc');
|
||||
|
||||
if (in_array('draft', $this->status)) {
|
||||
$query = $query->orWhere('status_id', Quote::STATUS_DRAFT);
|
||||
}
|
||||
|
||||
if (in_array('sent', $this->status)) {
|
||||
$query = $query->orWhere('status_id', Quote::STATUS_SENT);
|
||||
}
|
||||
|
||||
if (in_array('approved', $this->status)) {
|
||||
$query = $query->orWhere('status_id', Quote::STATUS_APPROVED);
|
||||
}
|
||||
|
||||
if (in_array('expired', $this->status)) {
|
||||
$query = $query->orWhere('status_id', Quote::STATUS_EXPIRED);
|
||||
if (count($this->status) > 0) {
|
||||
$query = $query->whereIn('status_id', $this->status);
|
||||
}
|
||||
|
||||
$query = $query
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\ClientPortal\Payments;
|
||||
|
||||
use App\Models\PaymentHash;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PaymentResponseRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'company_gateway_id' => 'required',
|
||||
'payment_hash' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function getPaymentHash()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
return PaymentHash::whereRaw("BINARY `hash`= ?", [$input['payment_hash']])->first();
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace App\Http\Requests\CompanyGateway;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
|
||||
use App\Models\Gateway;
|
||||
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
|
||||
|
||||
class StoreCompanyGatewayRequest extends Request
|
||||
@ -42,6 +43,22 @@ class StoreCompanyGatewayRequest extends Request
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
$gateway = Gateway::where('key', $input['gateway_key'])->first();
|
||||
|
||||
$default_gateway_fields = json_decode($gateway->fields);
|
||||
|
||||
/*Force gateway properties */
|
||||
if(isset($input['config']) && is_object(json_decode($input['config'])))
|
||||
{
|
||||
foreach(json_decode($input['config']) as $key => $value) {
|
||||
|
||||
$default_gateway_fields->{$key} = $value;
|
||||
|
||||
}
|
||||
|
||||
$input['config'] = json_encode($default_gateway_fields);
|
||||
}
|
||||
|
||||
|
||||
if (isset($input['config'])) {
|
||||
$input['config'] = encrypt($input['config']);
|
||||
@ -51,6 +68,7 @@ class StoreCompanyGatewayRequest extends Request
|
||||
$input['fees_and_limits'] = $this->cleanFeesAndLimits($input['fees_and_limits']);
|
||||
}
|
||||
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace App\Http\Requests\CompanyGateway;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
|
||||
use App\Models\Company;
|
||||
use App\Models\Gateway;
|
||||
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
|
||||
|
||||
class UpdateCompanyGatewayRequest extends Request
|
||||
@ -44,6 +45,21 @@ class UpdateCompanyGatewayRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
/*Force gateway properties */
|
||||
if(isset($input['config']) && is_object(json_decode($input['config'])) && array_key_exists('gateway_key', $input))
|
||||
{
|
||||
$gateway = Gateway::where('key', $input['gateway_key'])->first();
|
||||
$default_gateway_fields = json_decode($gateway->fields);
|
||||
|
||||
foreach(json_decode($input['config']) as $key => $value) {
|
||||
|
||||
$default_gateway_fields->{$key} = $value;
|
||||
|
||||
}
|
||||
|
||||
$input['config'] = json_encode($default_gateway_fields);
|
||||
}
|
||||
|
||||
$input['config'] = encrypt($input['config']);
|
||||
|
||||
if (isset($input['fees_and_limits'])) {
|
||||
|
@ -91,6 +91,7 @@ class EmailInvoice extends BaseMailerJob implements ShouldQueue
|
||||
catch (\Swift_TransportException $e) {
|
||||
|
||||
event(new InvoiceWasEmailedAndFailed($this->invoice_invitation->invoice, $this->company, $e->getMessage(), Ninja::eventVars()));
|
||||
|
||||
}
|
||||
|
||||
if (count(Mail::failures()) > 0) {
|
||||
|
@ -227,6 +227,14 @@ class Import implements ShouldQueue
|
||||
unset($data['account_id']);
|
||||
}
|
||||
|
||||
if(isset($data['referral_code'])) {
|
||||
$account = $this->company->account;
|
||||
$account->referral_code = $data['referral_code'];
|
||||
$account->save();
|
||||
|
||||
unset($data['referral_code']);
|
||||
}
|
||||
|
||||
$company_repository = new CompanyRepository();
|
||||
$company_repository->save($data, $this->company);
|
||||
|
||||
|
@ -44,28 +44,17 @@ class QuoteUpdatedActivity implements ShouldQueue
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$quote = $event->quote;
|
||||
|
||||
$invoices = $payment->invoices;
|
||||
|
||||
$fields = new \stdClass;
|
||||
|
||||
$fields->payment_id = $quote->id;
|
||||
$fields->client_id = $quote->client_id;
|
||||
$fields->user_id = $quote->user_id;
|
||||
$fields->quote_id = $quote->id;
|
||||
$fields->client_id = $quote->client_id;
|
||||
$fields->user_id = $quote->user_id;
|
||||
$fields->company_id = $quote->company_id;
|
||||
$fields->activity_type_id = Activity::UPDATE_QUOTE;
|
||||
|
||||
$this->activity_repo->save($fields, $quote, $event->event_vars);
|
||||
|
||||
// foreach ($invoices as $invoice) {
|
||||
// //todo we may need to add additional logic if in the future we apply payments to other entity Types, not just invoices
|
||||
// $fields->invoice_id = $invoice->id;
|
||||
|
||||
// $this->activity_repo->save($fields, $invoice, $event->event_vars);
|
||||
// }
|
||||
|
||||
// if (count($invoices) == 0) {
|
||||
// $this->activity_repo->save($fields, $payment, $event->event_vars);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +393,7 @@ class Company extends BaseModel
|
||||
|
||||
public function system_logs()
|
||||
{
|
||||
return $this->hasMany(SystemLog::class);
|
||||
return $this->hasMany(SystemLog::class)->orderBy('id', 'DESC')->take(50);
|
||||
}
|
||||
|
||||
public function tokens_hashed()
|
||||
|
@ -201,6 +201,23 @@ class CompanyGateway extends BaseModel
|
||||
return floatval($this->fee_amount) || floatval($this->fee_percent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current test mode of the gateway
|
||||
*
|
||||
* @return boolean whether the gateway is in testmode or not.
|
||||
*/
|
||||
public function isTestMode() :bool
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
|
||||
if($this->gateway->provider == 'Stripe' && strpos($config->publishableKey, 'test'))
|
||||
return true;
|
||||
|
||||
if($config && property_exists($config, 'testMode') && $config->testMode)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Get Publishable Key
|
||||
* Only works for STRIPE and PAYMILL
|
||||
@ -211,6 +228,20 @@ class CompanyGateway extends BaseModel
|
||||
return $this->getConfigField('publishableKey');
|
||||
}
|
||||
|
||||
public function getFeesAndLimits()
|
||||
{
|
||||
if (is_null($this->fees_and_limits))
|
||||
return false;
|
||||
|
||||
$fees_and_limits = new \stdClass;
|
||||
|
||||
foreach($this->fees_and_limits as $key => $value) {
|
||||
$fees_and_limits = $this->fees_and_limits->{$key};
|
||||
}
|
||||
|
||||
return $fees_and_limits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted fee amount for the gateway
|
||||
*
|
||||
@ -236,17 +267,13 @@ class CompanyGateway extends BaseModel
|
||||
return $label;
|
||||
}
|
||||
|
||||
public function calcGatewayFee($amount)
|
||||
public function calcGatewayFee($amount, $include_taxes = false)
|
||||
{
|
||||
if (is_null($this->fees_and_limits)) {
|
||||
|
||||
$fees_and_limits = $this->getFeesAndLimits();
|
||||
|
||||
if(!$fees_and_limits)
|
||||
return 0;
|
||||
}
|
||||
|
||||
$fees_and_limits = new \stdClass;
|
||||
|
||||
foreach($this->fees_and_limits as $key => $value) {
|
||||
$fees_and_limits = $this->fees_and_limits->{$key};
|
||||
}
|
||||
|
||||
$fee = 0;
|
||||
|
||||
@ -259,30 +286,68 @@ class CompanyGateway extends BaseModel
|
||||
$fee += $amount * $fees_and_limits->fee_percent / 100;
|
||||
info("fee after adding fee percent = {$fee}");
|
||||
}
|
||||
|
||||
$pre_tax_fee = $fee;
|
||||
|
||||
if ($fees_and_limits->fee_tax_rate1) {
|
||||
$fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate1 / 100;
|
||||
info("fee after adding fee tax 1 = {$fee}");
|
||||
}
|
||||
|
||||
if ($fees_and_limits->fee_tax_rate2) {
|
||||
$fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate2 / 100;
|
||||
info("fee after adding fee tax 2 = {$fee}");
|
||||
}
|
||||
|
||||
if ($fees_and_limits->fee_tax_rate3) {
|
||||
$fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate3 / 100;
|
||||
info("fee after adding fee tax 3 = {$fee}");
|
||||
}
|
||||
|
||||
/* Cap fee if we have to here. */
|
||||
if($fees_and_limits->fee_cap > 0 && ($fee > $fees_and_limits->fee_cap))
|
||||
$fee = $fees_and_limits->fee_cap;
|
||||
|
||||
$pre_tax_fee = $fee;
|
||||
|
||||
/**/
|
||||
if($include_taxes)
|
||||
{
|
||||
if ($fees_and_limits->fee_tax_rate1) {
|
||||
$fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate1 / 100;
|
||||
info("fee after adding fee tax 1 = {$fee}");
|
||||
}
|
||||
|
||||
if ($fees_and_limits->fee_tax_rate2) {
|
||||
$fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate2 / 100;
|
||||
info("fee after adding fee tax 2 = {$fee}");
|
||||
}
|
||||
|
||||
if ($fees_and_limits->fee_tax_rate3) {
|
||||
$fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate3 / 100;
|
||||
info("fee after adding fee tax 3 = {$fee}");
|
||||
}
|
||||
}
|
||||
|
||||
return $fee;
|
||||
}
|
||||
|
||||
/**
|
||||
* we need to average out the gateway fees across all the invoices
|
||||
* so lets iterate.
|
||||
*
|
||||
* we MAY need to adjust the final fee to ensure our rounding makes sense!
|
||||
*/
|
||||
public function calcGatewayFeeObject($amount, $invoice_count)
|
||||
{
|
||||
$total_gateway_fee = $this->calcGatewayFee($amount);
|
||||
|
||||
$fee_object = new \stdClass;
|
||||
|
||||
$fees_and_limits = $this->getFeesAndLimits();
|
||||
|
||||
if(!$fees_and_limits)
|
||||
return $fee_object;
|
||||
|
||||
$fee_component_amount = $fees_and_limits->fee_amount ?: 0;
|
||||
$fee_component_percent = $fees_and_limits->fee_percent ? ($amount * $fees_and_limits->fee_percent / 100) : 0;
|
||||
|
||||
$combined_fee_component = $fee_component_amount + $fee_component_percent;
|
||||
|
||||
$fee_component_tax_name1 = $fees_and_limits->fee_tax_name1 ?: '';
|
||||
$fee_component_tax_rate1 = $fees_and_limits->fee_tax_rate1 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate1 / 100) : 0;
|
||||
|
||||
$fee_component_tax_name2 = $fees_and_limits->fee_tax_name2 ?: '';
|
||||
$fee_component_tax_rate2 = $fees_and_limits->fee_tax_rate2 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate2 / 100) : 0;
|
||||
|
||||
$fee_component_tax_name3 = $fees_and_limits->fee_tax_name3 ?: '';
|
||||
$fee_component_tax_rate3 = $fees_and_limits->fee_tax_rate3 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate3 / 100) : 0;
|
||||
|
||||
}
|
||||
|
||||
public function resolveRouteBinding($value)
|
||||
{
|
||||
return $this
|
||||
|
@ -76,6 +76,7 @@ class Payment extends BaseModel
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'is_deleted' => 'bool',
|
||||
'meta' => 'object',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
|
29
app/Models/PaymentHash.php
Normal file
29
app/Models/PaymentHash.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PaymentHash extends Model
|
||||
{
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'object'
|
||||
];
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
@ -14,6 +14,8 @@ class CompanyGatewayObserver
|
||||
*/
|
||||
public function created(CompanyGateway $company_gateway)
|
||||
{
|
||||
|
||||
/* Set company gateway if not exists*/
|
||||
if(!$company_gateway->label){
|
||||
$company_gateway->label = $company_gateway->gateway->name;
|
||||
$company_gateway->save();
|
||||
|
@ -18,6 +18,7 @@ use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\AuthorizePaymentDriver;
|
||||
@ -46,6 +47,7 @@ class AuthorizeCreditCard
|
||||
|
||||
public function processPaymentView($data)
|
||||
{
|
||||
|
||||
$tokens = ClientGatewayToken::where('client_id', $this->authorize->client->id)
|
||||
->where('company_gateway_id', $this->authorize->company_gateway->id)
|
||||
->where('gateway_type_id', GatewayType::CREDIT_CARD)
|
||||
@ -62,6 +64,7 @@ class AuthorizeCreditCard
|
||||
|
||||
public function processPaymentResponse($request)
|
||||
{
|
||||
|
||||
if($request->token)
|
||||
return $this->processTokenPayment($request);
|
||||
|
||||
@ -71,14 +74,10 @@ class AuthorizeCreditCard
|
||||
|
||||
$gateway_customer_reference = $authorise_create_customer->create($data);
|
||||
|
||||
info($gateway_customer_reference);
|
||||
|
||||
$authorise_payment_method = new AuthorizePaymentMethod($this->authorize);
|
||||
|
||||
$payment_profile = $authorise_payment_method->addPaymentMethodToClient($gateway_customer_reference, $data);
|
||||
$payment_profile_id = $payment_profile->getPaymentProfile()->getCustomerPaymentProfileId();
|
||||
|
||||
info($request->input('store_card'));
|
||||
|
||||
if($request->has('store_card') && $request->input('store_card') === 'true'){
|
||||
$authorise_payment_method->payment_method = GatewayType::CREDIT_CARD;
|
||||
@ -93,23 +92,31 @@ class AuthorizeCreditCard
|
||||
|
||||
private function processTokenPayment($request)
|
||||
{
|
||||
|
||||
$client_gateway_token = ClientGatewayToken::find($this->decodePrimaryKey($request->token));
|
||||
|
||||
$data = (new ChargePaymentProfile($this->authorize))->chargeCustomerProfile($client_gateway_token->gateway_customer_reference, $client_gateway_token->token, $request->input('amount_with_fee'));
|
||||
|
||||
return $this->handleResponse($data, $request);
|
||||
|
||||
}
|
||||
|
||||
private function tokenBilling($cgt, $amount, $invoice)
|
||||
private function tokenBilling($cgt, $payment_hash)
|
||||
{
|
||||
$data = (new ChargePaymentProfile($this->authorize))->chargeCustomerProfile($cgt->gateway_customer_reference, $cgt->token, $amounts);
|
||||
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
|
||||
$data = (new ChargePaymentProfile($this->authorize))->chargeCustomerProfile($cgt->gateway_customer_reference, $cgt->token, $amount);
|
||||
|
||||
if($data['response'] != null && $data['response']->getMessages()->getResultCode() == "Ok") {
|
||||
|
||||
$payment = $this->createPaymentRecord($data, $amount);
|
||||
$payment->meta = $cgt->meta;
|
||||
$payment->save();
|
||||
|
||||
$this->authorize->attachInvoices($payment, $payment_hash);
|
||||
$payment->service()->updateInvoicePayment($payment_hash);
|
||||
|
||||
$this->authorize->attachInvoices($payment, $invoice->hashed_id);
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
$vars = [
|
||||
@ -136,12 +143,31 @@ class AuthorizeCreditCard
|
||||
|
||||
private function handleResponse($data, $request)
|
||||
{
|
||||
|
||||
$response = $data['response'];
|
||||
|
||||
if($response != null && $response->getMessages()->getResultCode() == "Ok")
|
||||
return $this->processSuccessfulResponse($data, $request);
|
||||
|
||||
return $this->processFailedResponse($data, $request);
|
||||
|
||||
}
|
||||
|
||||
private function storePayment($payment_hash, $data)
|
||||
{
|
||||
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
|
||||
$payment = $this->createPaymentRecord($data, $amount);
|
||||
|
||||
$this->authorize->attachInvoices($payment, $payment_hash);
|
||||
|
||||
$payment->service()->updateInvoicePayment($payment_hash);
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
return $payment;
|
||||
|
||||
}
|
||||
|
||||
private function createPaymentRecord($data, $amount) :?Payment
|
||||
@ -158,21 +184,18 @@ class AuthorizeCreditCard
|
||||
$payment->save();
|
||||
|
||||
return $payment;
|
||||
|
||||
}
|
||||
|
||||
private function processSuccessfulResponse($data, $request)
|
||||
{
|
||||
$payment = $this->createPaymentRecord($data, $request->input('amount_with_fee'));
|
||||
|
||||
$this->authorize->attachInvoices($payment, $request->hashed_ids);
|
||||
|
||||
$payment->service()->updateInvoicePayment();
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
$payment_hash = PaymentHash::whereRaw("BINARY `hash`= ?", [$request->input('payment_hash')])->firstOrFail();
|
||||
$payment = $this->storePayment($payment_hash, $data);
|
||||
|
||||
$vars = [
|
||||
'hashed_ids' => $request->input('hashed_ids'),
|
||||
'amount' => $request->input('amount')
|
||||
'invoices' => $payment_hash->invoices(),
|
||||
'amount' => array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total
|
||||
];
|
||||
|
||||
$logger_message = [
|
||||
@ -194,6 +217,7 @@ class AuthorizeCreditCard
|
||||
|
||||
private function formatGatewayResponse($data, $vars)
|
||||
{
|
||||
|
||||
$response = $data['response'];
|
||||
|
||||
return [
|
||||
@ -202,8 +226,9 @@ class AuthorizeCreditCard
|
||||
'auth_code' => $response->getTransactionResponse()->getAuthCode(),
|
||||
'code' => $response->getTransactionResponse()->getMessages()[0]->getCode(),
|
||||
'description' => $response->getTransactionResponse()->getMessages()[0]->getDescription(),
|
||||
'invoices' => $vars['hashed_ids'],
|
||||
'invoices' => $vars['invoices'],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -122,7 +122,7 @@ class AuthorizePaymentMethod
|
||||
|
||||
public function createClientGatewayToken($payment_profile, $gateway_customer_reference)
|
||||
{
|
||||
info(print_r($payment_profile,1));
|
||||
// info(print_r($payment_profile,1));
|
||||
|
||||
$client_gateway_token = new ClientGatewayToken();
|
||||
$client_gateway_token->company_id = $this->authorize->client->company_id;
|
||||
|
@ -16,6 +16,7 @@ use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\PaymentDrivers\Authorize\AuthorizeCreditCard;
|
||||
use App\PaymentDrivers\Authorize\AuthorizePaymentMethod;
|
||||
use App\PaymentDrivers\Authorize\ChargePaymentProfile;
|
||||
@ -143,11 +144,11 @@ class AuthorizePaymentDriver extends BaseDriver
|
||||
->first();
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, float $amount, ?Invoice $invoice = null)
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
$this->setPaymentMethod($cgt->gateway_type_id);
|
||||
|
||||
return $this->payment_method->tokenBilling($cgt, $amount, $invoice);
|
||||
return $this->payment_method->tokenBilling($cgt, $payment_hash);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,11 +14,13 @@ namespace App\PaymentDrivers;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasPaid;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\PaymentDrivers\AbstractPaymentDriver;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -109,25 +111,19 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
* @param array $hashed_ids The array of invoice hashed_ids
|
||||
* @return Payment The payment object
|
||||
*/
|
||||
public function attachInvoices(Payment $payment, $hashed_ids): Payment
|
||||
|
||||
public function attachInvoices(Payment $payment, PaymentHash $payment_hash): Payment
|
||||
{
|
||||
$transformed = $this->transformKeys($hashed_ids);
|
||||
$array = is_array($transformed) ? $transformed : [$transformed];
|
||||
|
||||
$invoices = Invoice::whereIn('id', $array)
|
||||
->whereClientId($this->client->id)
|
||||
->get();
|
||||
|
||||
$paid_invoices = $payment_hash->invoices();
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($paid_invoices, 'invoice_id')))->get();
|
||||
$payment->invoices()->sync($invoices);
|
||||
$payment->save();
|
||||
|
||||
$payment->service()->applyNumber()->save();
|
||||
|
||||
$invoices->each(function ($invoice) use($payment){
|
||||
event(new InvoiceWasPaid($invoice, $payment->company, Ninja::eventVars()));
|
||||
});
|
||||
|
||||
return $payment;
|
||||
return $payment->service()->applyNumber()->save();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,10 +148,45 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
/**
|
||||
* Process an unattended payment
|
||||
*
|
||||
* @param ClientGatewayToken $cgt The client gateway token object
|
||||
* @param float $amount The amount to bill
|
||||
* @param Invoice $invoice Optional Invoice object being paid
|
||||
* @return Response The payment response
|
||||
* @param ClientGatewayToken $cgt The client gateway token object
|
||||
* @param PaymentHash $payment_hash The Payment hash containing the payment meta data
|
||||
* @return Response The payment response
|
||||
*/
|
||||
public function tokenBilling(ClientGatewayToken $cgt, float $amount, ?Invoice $invoice = null) {}
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) {}
|
||||
|
||||
/**
|
||||
* When a successful payment is made, we need to append the gateway fee
|
||||
* to an invoice
|
||||
*
|
||||
* @param PaymentResponseRequest $request The incoming payment request
|
||||
* @return void Success/Failure
|
||||
*/
|
||||
public function confirmGatewayFee(PaymentResponseRequest $request) :void
|
||||
{
|
||||
/*Payment meta data*/
|
||||
$payment_hash = $request->getPaymentHash();
|
||||
|
||||
/*Payment invoices*/
|
||||
$payment_invoices = $payment_hash->invoices();
|
||||
|
||||
// /*Fee charged at gateway*/
|
||||
$fee_total = $payment_hash->fee_total;
|
||||
|
||||
// Sum of invoice amounts
|
||||
// $invoice_totals = array_sum(array_column($payment_invoices,'amount'));
|
||||
|
||||
/*Hydrate invoices*/
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get();
|
||||
|
||||
$invoices->each(function($invoice) use($fee_total){
|
||||
|
||||
if(collect($invoice->line_items)->contains('type_id', '3')){
|
||||
$invoice->service()->toggleFeesPaid()->save();
|
||||
$invoice->client->service()->updateBalance($fee_total)->save();
|
||||
$invoice->ledger()->updateInvoiceBalance($fee_total, $notes = 'Gateway fee adjustment');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,14 @@
|
||||
namespace App\PaymentDrivers;
|
||||
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SystemLogTrait;
|
||||
use Illuminate\Support\Carbon;
|
||||
@ -240,14 +242,13 @@ class BasePaymentDriver
|
||||
{
|
||||
$this->gateway();
|
||||
|
||||
$response = $this->gateway
|
||||
->purchase($data)
|
||||
->setItems($items)
|
||||
->send();
|
||||
$response = $this->gateway
|
||||
->purchase($data)
|
||||
->setItems($items)
|
||||
->send();
|
||||
|
||||
return $response;
|
||||
/*
|
||||
$this->purchaseResponse = (array)$response->getData();*/
|
||||
|
||||
}
|
||||
|
||||
public function completePurchase($data)
|
||||
@ -272,18 +273,51 @@ class BasePaymentDriver
|
||||
}
|
||||
|
||||
|
||||
public function attachInvoices(Payment $payment, $hashed_ids): Payment
|
||||
public function attachInvoices(Payment $payment, PaymentHash $payment_hash): Payment
|
||||
{
|
||||
$transformed = $this->transformKeys($hashed_ids);
|
||||
$array = is_array($transformed) ? $transformed : [$transformed];
|
||||
|
||||
$invoices = Invoice::whereIn('id', $array)
|
||||
->whereClientId($this->client->id)
|
||||
->get();
|
||||
|
||||
$paid_invoices = $payment_hash->invoices();
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($paid_invoices, 'invoice_id')))->get();
|
||||
$payment->invoices()->sync($invoices);
|
||||
$payment->save();
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a successful payment is made, we need to append the gateway fee
|
||||
* to an invoice
|
||||
*
|
||||
* @param PaymentResponseRequest $request The incoming payment request
|
||||
* @return void Success/Failure
|
||||
*/
|
||||
public function confirmGatewayFee(PaymentResponseRequest $request) :void
|
||||
{
|
||||
/*Payment meta data*/
|
||||
$payment_hash = $request->getPaymentHash();
|
||||
|
||||
/*Payment invoices*/
|
||||
$payment_invoices = $payment_hash->invoices();
|
||||
|
||||
// /*Fee charged at gateway*/
|
||||
$fee_total = $payment_hash->fee_total;
|
||||
|
||||
// Sum of invoice amounts
|
||||
// $invoice_totals = array_sum(array_column($payment_invoices,'amount'));
|
||||
|
||||
/*Hydrate invoices*/
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get();
|
||||
|
||||
$invoices->each(function($invoice) use($fee_total){
|
||||
|
||||
if(collect($invoice->line_items)->contains('type_id', '3')){
|
||||
$invoice->service()->toggleFeesPaid()->save();
|
||||
$invoice->client->service()->updateBalance($fee_total)->save();
|
||||
$invoice->ledger()->updateInvoiceBalance($fee_total, $notes = 'Gateway fee adjustment');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\CheckoutCom\Utilities;
|
||||
@ -109,6 +110,7 @@ class CheckoutComPaymentDriver extends BasePaymentDriver
|
||||
'value' => $request->value,
|
||||
'raw_value' => $request->raw_value,
|
||||
'currency' => $request->currency,
|
||||
'payment_hash' =>$request->payment_hash,
|
||||
];
|
||||
|
||||
$state = array_merge($state, $request->all());
|
||||
@ -163,10 +165,9 @@ class CheckoutComPaymentDriver extends BasePaymentDriver
|
||||
];
|
||||
|
||||
$payment = $this->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
|
||||
$this->attachInvoices($payment, $state['hashed_ids']);
|
||||
|
||||
$payment->service()->updateInvoicePayment();
|
||||
$payment_hash = PaymentHash::whereRaw("BINARY `hash`= ?", [$state['payment_hash']])->firstOrFail();
|
||||
$this->attachInvoices($payment, $payment_hash);
|
||||
$payment->service()->updateInvoicePayment($payment_hash);
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
@ -317,6 +318,6 @@ class CheckoutComPaymentDriver extends BasePaymentDriver
|
||||
}
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, float $amount) {}
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) {}
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\Utils\Ninja;
|
||||
@ -91,7 +92,7 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
|
||||
* @var $data['amount_with_fee']
|
||||
* @var $data['token']
|
||||
* @var $data['payment_method_id']
|
||||
* @var $data['hashed_ids']
|
||||
* @var $data['payment_hash']
|
||||
*
|
||||
* @param array $data variables required to build payment page
|
||||
* @return view Gateway and payment method specific view
|
||||
@ -163,10 +164,9 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
|
||||
}
|
||||
|
||||
$payment = $this->createPayment($response->getData());
|
||||
|
||||
$this->attachInvoices($payment, $request->input('hashed_ids'));
|
||||
|
||||
$payment->service()->UpdateInvoicePayment();
|
||||
$payment_hash = PaymentHash::whereRaw("BINARY `hash`= ?", [$request->input('payment_hash')])->firstOrFail();
|
||||
$this->attachInvoices($payment, $payment_hash);
|
||||
$payment->service()->updateInvoicePayment($payment_hash);
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
@ -194,7 +194,7 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
|
||||
{
|
||||
$url = $this->client->company->domain() . "/client/payments/process/response";
|
||||
$url .= "?company_gateway_id={$this->company_gateway->id}&gateway_type_id=" . GatewayType::PAYPAL;
|
||||
$url .= "&hashed_ids=" . implode(",", $input['hashed_ids']);
|
||||
$url .= "&payment_hash=" . $input['payment_hash'];
|
||||
$url .= "&amount=" . $input['amount'];
|
||||
$url .= "&fee=" . $input['fee'];
|
||||
|
||||
|
@ -16,6 +16,7 @@ use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\StripePaymentDriver;
|
||||
@ -35,9 +36,12 @@ class Charge
|
||||
* Create a charge against a payment method
|
||||
* @return bool success/failure
|
||||
*/
|
||||
public function tokenBilling(ClientGatewayToken $cgt, $amount, ?Invoice $invoice)
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
$invoice = sInvoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->first();
|
||||
|
||||
if($invoice)
|
||||
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
else
|
||||
@ -169,11 +173,12 @@ class Charge
|
||||
];
|
||||
|
||||
$payment = $this->stripe->createPaymentRecord($data, $amount);
|
||||
$payment->meta = $cgt->meta;
|
||||
$payment->save();
|
||||
|
||||
if($invoice)
|
||||
$this->stripe->attachInvoices($payment, $invoice->hashed_id);
|
||||
$this->stripe->attachInvoices($payment, $payment_hash);
|
||||
|
||||
$payment->service()->updateInvoicePayment();
|
||||
$payment->service()->updateInvoicePayment($payment_hash);
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
|
@ -19,6 +19,7 @@ use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\StripePaymentDriver;
|
||||
@ -92,7 +93,7 @@ class CreditCard
|
||||
'amount' => $this->stripe->convertToStripeAmount($data['amount_with_fee'], $this->stripe->client->currency()->precision),
|
||||
'currency' => $this->stripe->client->getCurrencyCode(),
|
||||
'customer' => $this->stripe->findOrCreateCustomer(),
|
||||
'description' => $data['invoices']->pluck('id'), //todo more meaningful description here:
|
||||
'description' => collect($data['invoices'])->pluck('id'), //todo more meaningful description here:
|
||||
];
|
||||
|
||||
if ($data['token']) {
|
||||
@ -113,6 +114,8 @@ class CreditCard
|
||||
{
|
||||
$server_response = json_decode($request->input('gateway_response'));
|
||||
|
||||
$payment_hash = PaymentHash::whereRaw("BINARY `hash`= ?", [$request->input('payment_hash')])->firstOrFail();
|
||||
|
||||
$state = [
|
||||
'payment_method' => $server_response->payment_method,
|
||||
'payment_status' => $server_response->status,
|
||||
@ -120,9 +123,11 @@ class CreditCard
|
||||
'gateway_type_id' => $request->payment_method_id,
|
||||
'hashed_ids' => $request->hashed_ids,
|
||||
'server_response' => $server_response,
|
||||
'payment_hash' => $payment_hash,
|
||||
];
|
||||
|
||||
$invoices = Invoice::whereIn('id', $this->stripe->transformKeys($state['hashed_ids']))
|
||||
/*Hydrate the invoices from the payment hash*/
|
||||
$invoices = Invoice::whereIn('id', $this->stripe->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))
|
||||
->whereClientId($this->stripe->client->id)
|
||||
->get();
|
||||
|
||||
@ -138,6 +143,10 @@ class CreditCard
|
||||
$state['customer'] = $state['payment_intent']->customer;
|
||||
|
||||
if ($state['payment_status'] == 'succeeded') {
|
||||
|
||||
/* Add gateway fees if needed! */
|
||||
$this->stripe->confirmGatewayFee($request);
|
||||
|
||||
return $this->processSuccessfulPayment($state);
|
||||
}
|
||||
|
||||
@ -161,6 +170,13 @@ class CreditCard
|
||||
'type' => $payment_method_object['type'],
|
||||
];
|
||||
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->exp_month = $payment_method_object['card']['exp_month'];
|
||||
$payment_meta->exp_year = $payment_method_object['card']['exp_year'];
|
||||
$payment_meta->brand = $payment_method_object['card']['brand'];
|
||||
$payment_meta->last4 = $payment_method_object['card']['last4'];
|
||||
$payment_meta->type = $payment_method_object['type'];
|
||||
|
||||
$payment_type = PaymentType::parseCardType($payment_method_object['card']['brand']);
|
||||
|
||||
if ($state['save_card'] == true) {
|
||||
@ -180,10 +196,11 @@ class CreditCard
|
||||
];
|
||||
|
||||
$payment = $this->stripe->createPayment($data, $status = Payment::STATUS_COMPLETED);
|
||||
$payment->meta = $payment_meta;
|
||||
|
||||
$this->stripe->attachInvoices($payment, $state['hashed_ids']);
|
||||
$payment = $this->stripe->attachInvoices($payment, $state['payment_hash']);
|
||||
|
||||
$payment->service()->updateInvoicePayment();
|
||||
$payment->service()->updateInvoicePayment($state['payment_hash']);
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
|
@ -23,6 +23,7 @@ use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Stripe\ACH;
|
||||
@ -367,9 +368,9 @@ class StripePaymentDriver extends BasePaymentDriver
|
||||
return response([], 200);
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, float $amount, ?Invoice $invoice = null)
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
return (new Charge($this))->tokenBilling($cgt, $amount, $invoice);
|
||||
return (new Charge($this))->tokenBilling($cgt, $payment_hash);
|
||||
}
|
||||
|
||||
/**
|
||||
|
132
app/Services/Invoice/AddGatewayFee.php
Normal file
132
app/Services/Invoice/AddGatewayFee.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Models\Client;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Services\AbstractService;
|
||||
use App\Services\Client\ClientService;
|
||||
use App\Services\Payment\PaymentService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
|
||||
class AddGatewayFee extends AbstractService
|
||||
{
|
||||
|
||||
private $company_gateway;
|
||||
|
||||
private $invoice;
|
||||
|
||||
private $amount;
|
||||
|
||||
public function __construct(CompanyGateway $company_gateway, Invoice $invoice, float $amount)
|
||||
{
|
||||
$this->company_gateway = $company_gateway;
|
||||
|
||||
$this->invoice = $invoice;
|
||||
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$gateway_fee = round($this->company_gateway->calcGatewayFee($this->amount), $this->invoice->client->currency()->precision);
|
||||
|
||||
$this->cleanPendingGatewayFees();
|
||||
|
||||
if($gateway_fee > 0)
|
||||
return $this->processGatewayFee($gateway_fee);
|
||||
|
||||
return $this->processGatewayDiscount($gateway_fee);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function cleanPendingGatewayFees()
|
||||
{
|
||||
$invoice_items = $this->invoice->line_items;
|
||||
|
||||
$invoice_items = collect($invoice_items)->filter(function ($item){
|
||||
return $item->type_id != '3';
|
||||
});
|
||||
|
||||
$this->invoice->line_items = $invoice_items;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function processGatewayFee($gateway_fee)
|
||||
{
|
||||
$invoice_item = new InvoiceItem;
|
||||
$invoice_item->type_id = '3';
|
||||
$invoice_item->product_key = ctrans('texts.surcharge');
|
||||
$invoice_item->notes = ctrans('texts.online_payment_surcharge');
|
||||
$invoice_item->quantity = 1;
|
||||
$invoice_item->cost = $gateway_fee;
|
||||
|
||||
if($fees_and_limits = $this->company_gateway->getFeesAndLimits())
|
||||
{
|
||||
$invoice_item->tax_rate1 = $fees_and_limits->fee_tax_rate1;
|
||||
$invoice_item->tax_rate2 = $fees_and_limits->fee_tax_rate2;
|
||||
$invoice_item->tax_rate3 = $fees_and_limits->fee_tax_rate3;
|
||||
}
|
||||
|
||||
$invoice_items = $this->invoice->line_items;
|
||||
$invoice_items[] = $invoice_item;
|
||||
|
||||
$this->invoice->line_items = $invoice_items;
|
||||
|
||||
/**Refresh Invoice values*/
|
||||
$this->invoice = $this->invoice->calc()->getInvoice();
|
||||
|
||||
/*Update client balance*/ // don't increment until we have process the payment!
|
||||
//$this->invoice->client->service()->updateBalance($gateway_fee)->save();
|
||||
//$this->invoice->ledger()->updateInvoiceBalance($gateway_fee, $notes = 'Gateway fee adjustment');
|
||||
|
||||
return $this->invoice;
|
||||
|
||||
}
|
||||
|
||||
private function processGatewayDiscount($gateway_fee)
|
||||
{
|
||||
$invoice_item = new InvoiceItem;
|
||||
$invoice_item->type_id = '3';
|
||||
$invoice_item->product_key = ctrans('texts.discount');
|
||||
$invoice_item->notes = ctrans('texts.online_payment_discount');
|
||||
$invoice_item->quantity = 1;
|
||||
$invoice_item->cost = $gateway_fee;
|
||||
|
||||
if($fees_and_limits = $this->company_gateway->getFeesAndLimits())
|
||||
{
|
||||
$invoice_item->tax_rate1 = $fees_and_limits->fee_tax_rate1;
|
||||
$invoice_item->tax_rate2 = $fees_and_limits->fee_tax_rate2;
|
||||
$invoice_item->tax_rate3 = $fees_and_limits->fee_tax_rate3;
|
||||
}
|
||||
|
||||
$invoice_items = $this->invoice->line_items;
|
||||
$invoice_items[] = $invoice_item;
|
||||
|
||||
$this->invoice->line_items = $invoice_items;
|
||||
|
||||
$this->invoice = $this->invoice->calc()->getInvoice();
|
||||
|
||||
// $this->invoice->client->service()->updateBalance($gateway_fee)->save();
|
||||
|
||||
// $this->invoice->ledger()->updateInvoiceBalance($gateway_fee, $notes = 'Discount fee adjustment');
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
}
|
@ -17,10 +17,12 @@ use App\Factory\PaymentFactory;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Services\AbstractService;
|
||||
use App\Services\Client\ClientService;
|
||||
use App\Services\Payment\PaymentService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AutoBillInvoice extends AbstractService
|
||||
{
|
||||
@ -55,31 +57,39 @@ class AutoBillInvoice extends AbstractService
|
||||
|
||||
if($this->invoice->partial > 0){
|
||||
$fee = $gateway_token->gateway->calcGatewayFee($this->invoice->partial);
|
||||
$amount = $this->invoice->partial + $fee;
|
||||
// $amount = $this->invoice->partial + $fee;
|
||||
$amount = $this->invoice->partial;
|
||||
}
|
||||
else{
|
||||
$fee = $gateway_token->gateway->calcGatewayFee($this->invoice->balance);
|
||||
$amount = $this->invoice->balance + $fee;
|
||||
// $amount = $this->invoice->balance + $fee;
|
||||
$amount = $this->invoice->balance;
|
||||
}
|
||||
|
||||
/* Make sure we remove any stale fees*/
|
||||
$this->purgeStaleGatewayFees();
|
||||
$payment_hash = PaymentHash::create([
|
||||
'hash' => Str::random(128),
|
||||
'data' => ['invoice_id' => $this->invoice->hashed_id, 'amount' => $amount],
|
||||
'fee_total' => $fee,
|
||||
'fee_invoice_id' => $this->invoice->id,
|
||||
]);
|
||||
|
||||
if($fee > 0)
|
||||
$this->addFeeToInvoice($fee);
|
||||
$payment = $gateway_token->gateway->driver($this->client)->tokenBilling($gateway_token, $payment_hash);
|
||||
|
||||
$payment = $gateway_token->gateway->driver($this->client)->tokenBilling($gateway_token, $amount, $this->invoice);
|
||||
//this is redundant - taken care of much further down.
|
||||
// if($payment){
|
||||
|
||||
if($payment){
|
||||
|
||||
$this->invoice = $this->invoice->service()->toggleFeesPaid()->save();
|
||||
// if($this->invoice->partial > 0)
|
||||
// $amount = $this->invoice->partial;
|
||||
// else
|
||||
// $amount = $this->invoice->balance;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO autobill failed
|
||||
}
|
||||
// $this->invoice = $this->invoice->service()->addGatewayFee($gateway_token->gateway, $amount)->save();
|
||||
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //TODO autobill failed
|
||||
// }
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
@ -144,34 +154,34 @@ class AutoBillInvoice extends AbstractService
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
private function purgeStaleGatewayFees()
|
||||
{
|
||||
$starting_amount = $this->invoice->amount;
|
||||
// private function purgeStaleGatewayFees()
|
||||
// {
|
||||
// $starting_amount = $this->invoice->amount;
|
||||
|
||||
$line_items = $this->invoice->line_items;
|
||||
// $line_items = $this->invoice->line_items;
|
||||
|
||||
$new_items = [];
|
||||
// $new_items = [];
|
||||
|
||||
foreach($line_items as $item)
|
||||
{
|
||||
// foreach($line_items as $item)
|
||||
// {
|
||||
|
||||
if($item->type_id != 3)
|
||||
$new_items[] = $item;
|
||||
// if($item->type_id != 3)
|
||||
// $new_items[] = $item;
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
$this->invoice->line_items = $new_items;
|
||||
$this->invoice->save();
|
||||
// $this->invoice->line_items = $new_items;
|
||||
// $this->invoice->save();
|
||||
|
||||
$this->invoice = $this->invoice->calc()->getInvoice();
|
||||
// $this->invoice = $this->invoice->calc()->getInvoice();
|
||||
|
||||
if($starting_amount != $this->invoice->amount && $this->invoice->status_id != Invoice::STATUS_DRAFT){
|
||||
$this->invoice->client->service()->updateBalance($this->invoice->amount - $starting_amount)->save();
|
||||
$this->invoice->ledger()->updateInvoiceBalance($this->invoice->amount - $starting_amount, 'Invoice balance updated after stale gateway fee removed')->save();
|
||||
}
|
||||
// if($starting_amount != $this->invoice->amount && $this->invoice->status_id != Invoice::STATUS_DRAFT){
|
||||
// $this->invoice->client->service()->updateBalance($this->invoice->amount - $starting_amount)->save();
|
||||
// $this->invoice->ledger()->updateInvoiceBalance($this->invoice->amount - $starting_amount, 'Invoice balance updated after stale gateway fee removed')->save();
|
||||
// }
|
||||
|
||||
return $this;
|
||||
}
|
||||
// return $this;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Checks whether a given gateway token is able
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Services\Client\ClientService;
|
||||
@ -76,11 +77,20 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addGatewayFee(CompanyGateway $company_gateway, float $amount)
|
||||
{
|
||||
|
||||
$this->invoice = (new AddGatewayFee($company_gateway, $this->invoice, $amount))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Update an invoice balance
|
||||
*
|
||||
* @param float $balance_adjustment The amount to adjust the invoice by
|
||||
* a negative amount will REDUCE the invoice balance, a positive amount will INCREASE
|
||||
* the invoice balance
|
||||
*
|
||||
* @return InvoiceService Parent class object
|
||||
*/
|
||||
public function updateBalance($balance_adjustment)
|
||||
@ -182,8 +192,8 @@ class InvoiceService
|
||||
$this->invoice->line_items = collect($this->invoice->line_items)
|
||||
->map(function ($item) {
|
||||
|
||||
if($item->type_id == 3)
|
||||
$item->type_id = 4;
|
||||
if($item->type_id == '3')
|
||||
$item->type_id = '4';
|
||||
|
||||
return $item;
|
||||
|
||||
@ -198,13 +208,14 @@ class InvoiceService
|
||||
$this->invoice->line_items = collect($this->invoice->line_items)
|
||||
->reject(function ($item) {
|
||||
|
||||
return $item->type_id == 3;
|
||||
return $item->type_id == '3';
|
||||
|
||||
})->toArray();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*Set partial value and due date to null*/
|
||||
public function clearPartial()
|
||||
{
|
||||
$this->invoice->partial = null;
|
||||
@ -213,6 +224,7 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*Update the partial amount of a invoice*/
|
||||
public function updatePartial($amount)
|
||||
{
|
||||
$this->invoice->partial += $amount;
|
||||
@ -220,6 +232,31 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*When a reminder is sent we want to touch the dates they were sent*/
|
||||
public function touchReminder(string $reminder_template)
|
||||
{
|
||||
switch ($reminder_template) {
|
||||
case 'reminder1':
|
||||
$this->invoice->reminder1_sent = now()->format('Y-m-d');
|
||||
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
|
||||
break;
|
||||
case 'reminder2':
|
||||
$this->invoice->reminder2_sent = now()->format('Y-m-d');
|
||||
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
|
||||
break;
|
||||
case 'reminder3':
|
||||
$this->invoice->reminder3_sent = now()->format('Y-m-d');
|
||||
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
|
||||
break;
|
||||
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@ -56,6 +56,10 @@ class TriggeredActions extends AbstractService
|
||||
$this->sendEmail();
|
||||
}
|
||||
|
||||
if($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true'){
|
||||
$this->invoice = $this->invoice->service()->markSent()->save();
|
||||
}
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
|
@ -37,8 +37,6 @@ class UpdateBalance extends AbstractService
|
||||
|
||||
if ($this->invoice->balance == 0) {
|
||||
$this->invoice->status_id = Invoice::STATUS_PAID;
|
||||
// $this->save();
|
||||
// event(new InvoiceWasPaid($this, $this->company));
|
||||
}
|
||||
|
||||
return $this->invoice;
|
||||
|
@ -84,9 +84,9 @@ class PaymentService
|
||||
return (new DeletePayment($this->payment))->run();
|
||||
}
|
||||
|
||||
public function updateInvoicePayment() :?Payment
|
||||
public function updateInvoicePayment($payment_hash = null) :?Payment
|
||||
{
|
||||
return ((new UpdateInvoicePayment($this->payment)))->run();
|
||||
return ((new UpdateInvoicePayment($this->payment, $payment_hash)))->run();
|
||||
}
|
||||
|
||||
public function applyNumber()
|
||||
|
@ -12,125 +12,101 @@
|
||||
|
||||
namespace App\Services\Payment;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasUpdated;
|
||||
use App\Helpers\Email\PaymentEmail;
|
||||
use App\Jobs\Payment\EmailPayment;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\SystemLog;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class UpdateInvoicePayment
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* @deprecated This is bad logic, assumes too much.
|
||||
*/
|
||||
public $payment;
|
||||
|
||||
public function __construct($payment)
|
||||
public $payment_hash;
|
||||
|
||||
public function __construct($payment, $payment_hash)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
$this->payment_hash = $payment_hash;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$invoices = $this->payment->invoices()->get();
|
||||
|
||||
$invoices_total = $invoices->sum('balance');
|
||||
$paid_invoices = $this->payment_hash->invoices();
|
||||
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($paid_invoices, 'invoice_id')))->get();
|
||||
|
||||
/* Simplest scenario - All invoices are paid in full*/
|
||||
if (strval($invoices_total) === strval($this->payment->amount)) {
|
||||
$invoices->each(function ($invoice) {
|
||||
$this->payment
|
||||
->ledger()
|
||||
->updatePaymentBalance($invoice->balance*-1);
|
||||
|
||||
$this->payment->client
|
||||
->service()
|
||||
->updateBalance($invoice->balance*-1)
|
||||
->updatePaidToDate($invoice->balance)
|
||||
->save();
|
||||
|
||||
$invoice->pivot->amount = $invoice->balance;
|
||||
$invoice->pivot->save();
|
||||
collect($paid_invoices)->each(function ($paid_invoice) use($invoices) {
|
||||
|
||||
$invoice->service()
|
||||
->clearPartial()
|
||||
->updateBalance($invoice->balance*-1)
|
||||
->save();
|
||||
$invoice = $invoices->first(function ($inv) use($paid_invoice) {
|
||||
return $paid_invoice->invoice_id == $inv->hashed_id;
|
||||
});
|
||||
}
|
||||
/*Combination of partials and full invoices are being paid*/
|
||||
else {
|
||||
$total = 0;
|
||||
|
||||
/* Calculate the grand total of the invoices*/
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($invoice->hasPartial()) {
|
||||
$total += $invoice->partial;
|
||||
} else {
|
||||
$total += $invoice->balance;
|
||||
}
|
||||
}
|
||||
if($invoice->id == $this->payment_hash->fee_invoice_id)
|
||||
$paid_amount = $paid_invoice->amount + $this->payment_hash->fee_total;
|
||||
else
|
||||
$paid_amount = $paid_invoice->amount;
|
||||
|
||||
/*Test if there is a batch of partial invoices that have been paid */
|
||||
if ($this->payment->amount == $total) {
|
||||
$invoices->each(function ($invoice) {
|
||||
if ($invoice->hasPartial()) {
|
||||
$this->payment
|
||||
->ledger()
|
||||
->updatePaymentBalance($invoice->partial*-1);
|
||||
$this->payment
|
||||
->ledger()
|
||||
->updatePaymentBalance($paid_amount*-1);
|
||||
|
||||
$this->payment->client->service()
|
||||
->updateBalance($invoice->partial*-1)
|
||||
->updatePaidToDate($invoice->partial)
|
||||
->save();
|
||||
$this->payment
|
||||
->client
|
||||
->service()
|
||||
->updateBalance($paid_amount*-1)
|
||||
->updatePaidToDate($paid_amount)
|
||||
->save();
|
||||
|
||||
$invoice->pivot->amount = $invoice->partial;
|
||||
$invoice->pivot->save();
|
||||
$pivot_invoice = $this->payment->invoices->first(function ($inv) use($paid_invoice){
|
||||
return $inv->hashed_id == $paid_invoice->invoice_id;
|
||||
});
|
||||
|
||||
$invoice->service()->updateBalance($invoice->partial*-1)
|
||||
->clearPartial()
|
||||
->setDueDate()
|
||||
->setStatus(Invoice::STATUS_PARTIAL)
|
||||
->save();
|
||||
} else {
|
||||
$this->payment
|
||||
->ledger()
|
||||
->updatePaymentBalance($invoice->balance*-1);
|
||||
/*update paymentable record*/
|
||||
$pivot_invoice->pivot->amount = $paid_amount;
|
||||
$pivot_invoice->save();
|
||||
|
||||
$this->payment->client->service()
|
||||
->updateBalance($invoice->balance*-1)
|
||||
->updatePaidToDate($invoice->balance)
|
||||
->save();
|
||||
$invoice->service() //caution what if we amount paid was less than partial - we wipe it!
|
||||
->clearPartial()
|
||||
->updateBalance($paid_amount*-1)
|
||||
->save();
|
||||
|
||||
$invoice->pivot->amount = $invoice->balance;
|
||||
$invoice->pivot->save();
|
||||
|
||||
$invoice->service()->clearPartial()->updateBalance($invoice->balance*-1)->save();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
SystemLogger::dispatch(
|
||||
[
|
||||
'payment' => $this->payment,
|
||||
'invoices' => $invoices,
|
||||
'invoices_total' => $invoices_total,
|
||||
'payment_amount' => $this->payment->amount,
|
||||
'partial_check_amount' => $total,
|
||||
],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_PAYMENT_RECONCILIATION_FAILURE,
|
||||
SystemLog::TYPE_LEDGER,
|
||||
$this->payment->client
|
||||
);
|
||||
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars()));
|
||||
|
||||
throw new \Exception("payment amount {$this->payment->amount} does not match invoice totals {$invoices_total} reversing payment");
|
||||
});
|
||||
|
||||
$this->payment->invoice()->delete();
|
||||
$this->payment->is_deleted=true;
|
||||
$this->payment->save();
|
||||
$this->payment->delete();
|
||||
}
|
||||
}
|
||||
// } else {
|
||||
// SystemLogger::dispatch(
|
||||
// [
|
||||
// 'payment' => $this->payment,
|
||||
// 'invoices' => $invoices,
|
||||
// 'invoices_total' => $invoices_total,
|
||||
// 'payment_amount' => $this->payment->amount,
|
||||
// 'partial_check_amount' => $total,
|
||||
// ],
|
||||
// SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
// SystemLog::EVENT_PAYMENT_RECONCILIATION_FAILURE,
|
||||
// SystemLog::TYPE_LEDGER,
|
||||
// $this->payment->client
|
||||
// );
|
||||
|
||||
// throw new \Exception("payment amount {$this->payment->amount} does not match invoice totals {$invoices_total} reversing payment");
|
||||
|
||||
// $this->payment->invoice()->delete();
|
||||
// $this->payment->is_deleted=true;
|
||||
// $this->payment->save();
|
||||
// $this->payment->delete();
|
||||
// }
|
||||
|
||||
|
||||
return $this->payment;
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ class CompanyGatewayTransformer extends EntityTransformer
|
||||
'custom_value4' => $company_gateway->custom_value4 ?: '',
|
||||
'label' => (string)$company_gateway->label ?: '',
|
||||
'token_billing' => (string)$company_gateway->token_billing,
|
||||
'test_mode' => (bool)$company_gateway->isTestMode(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -33,12 +33,12 @@ class Number
|
||||
/**
|
||||
* Formats a given value based on the clients currency
|
||||
*
|
||||
* @param float $value The number to be formatted
|
||||
* @param float $value The number to be formatted
|
||||
* @param object $currency The client currency object
|
||||
*
|
||||
* @return float The formatted value
|
||||
* @return string The formatted value
|
||||
*/
|
||||
public static function formatValue($value, $currency) : float
|
||||
public static function formatValue($value, $currency) :string
|
||||
{
|
||||
$value = floatval($value);
|
||||
|
||||
@ -49,6 +49,30 @@ class Number
|
||||
return number_format($value, $precision, $decimal, $thousand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a given value based on the clients currency
|
||||
* BACK to a float
|
||||
*
|
||||
* @param string $value The formatted number to be converted back to float
|
||||
* @param object $currency The client currency object
|
||||
*
|
||||
* @return float The formatted value
|
||||
*/
|
||||
public static function parseFloat($value)
|
||||
{
|
||||
// convert "," to "."
|
||||
$s = str_replace(',', '.', $value);
|
||||
|
||||
// remove everything except numbers and dot "."
|
||||
$s = preg_replace("/[^0-9\.]/", "", $s);
|
||||
|
||||
// remove all seperators from first part and keep the end
|
||||
$s = str_replace('.', '',substr($s, 0, -3)) . substr($s, -3);
|
||||
|
||||
// return float
|
||||
return (float) $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a given value based on the clients currency AND country
|
||||
*
|
||||
|
@ -71,8 +71,8 @@ class SystemHealth
|
||||
'env_writable' => self::checkEnvWritable(),
|
||||
//'mail' => self::testMailServer(),
|
||||
'simple_db_check' => (bool) self::simpleDbCheck(),
|
||||
'npm_status' => self::checkNpm(),
|
||||
'node_status' => self::checkNode(),
|
||||
//'npm_status' => self::checkNpm(),
|
||||
//'node_status' => self::checkNode(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,26 @@ class AddIsPublicToDocumentsTable extends Migration
|
||||
$table->softDeletes('deleted_at', 6);
|
||||
});
|
||||
|
||||
Schema::create('payment_hashes', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->string('hash', 255);
|
||||
$table->decimal('fee_total', 16, 4);
|
||||
$table->unsignedInteger('fee_invoice_id')->nullable();
|
||||
$table->mediumText('data');
|
||||
$table->timestamps(6);
|
||||
});
|
||||
|
||||
Schema::table('recurring_invoices', function ($table) {
|
||||
$table->string('auto_bill');
|
||||
});
|
||||
|
||||
// Schema::table('recurring_expenses', function ($table) {
|
||||
// $table->string('auto_bill');
|
||||
// });
|
||||
|
||||
Schema::table('companies', function ($table) {
|
||||
$table->enum('default_auto_bill', ['off', 'always','optin','optout'])->default('off');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ use App\Models\GatewayType;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\Quote;
|
||||
use App\Models\User;
|
||||
@ -30,6 +31,7 @@ use App\Utils\Ninja;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RandomDataSeeder extends Seeder
|
||||
{
|
||||
@ -213,9 +215,16 @@ class RandomDataSeeder extends Seeder
|
||||
|
||||
$payment->invoices()->save($invoice);
|
||||
|
||||
$payment_hash = new PaymentHash;
|
||||
$payment_hash->hash = Str::random(128);
|
||||
$payment_hash->data = [['invoice_id' => $invoice->hashed_id, 'amount' => $invoice->balance]];
|
||||
$payment_hash->fee_total = 0;
|
||||
$payment_hash->fee_invoice_id = $invoice->id;
|
||||
$payment_hash->save();
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
$payment->service()->updateInvoicePayment();
|
||||
$payment->service()->updateInvoicePayment($payment_hash);
|
||||
|
||||
// UpdateInvoicePayment::dispatchNow($payment, $payment->company);
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
{"assets/images/payment_types/ach.png":["assets/images/payment_types/ach.png"],"assets/images/payment_types/amex.png":["assets/images/payment_types/amex.png"],"assets/images/payment_types/carteblanche.png":["assets/images/payment_types/carteblanche.png"],"assets/images/payment_types/dinerscard.png":["assets/images/payment_types/dinerscard.png"],"assets/images/payment_types/discover.png":["assets/images/payment_types/discover.png"],"packages/font_awesome_flutter/lib/fonts/fa-brands-400.ttf":["packages/font_awesome_flutter/lib/fonts/fa-brands-400.ttf"],"packages/font_awesome_flutter/lib/fonts/fa-regular-400.ttf":["packages/font_awesome_flutter/lib/fonts/fa-regular-400.ttf"],"packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf":["packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf"],"assets/images/google-icon.png":["assets/images/google-icon.png"],"assets/images/payment_types/jcb.png":["assets/images/payment_types/jcb.png"],"assets/images/payment_types/laser.png":["assets/images/payment_types/laser.png"],"assets/images/logo.png":["assets/images/logo.png"],"assets/images/payment_types/maestro.png":["assets/images/payment_types/maestro.png"],"assets/images/payment_types/mastercard.png":["assets/images/payment_types/mastercard.png"],"packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf":["packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf"],"assets/images/payment_types/other.png":["assets/images/payment_types/other.png"],"assets/images/payment_types/paypal.png":["assets/images/payment_types/paypal.png"],"assets/images/payment_types/solo.png":["assets/images/payment_types/solo.png"],"assets/images/payment_types/switch.png":["assets/images/payment_types/switch.png"],"assets/images/payment_types/unionpay.png":["assets/images/payment_types/unionpay.png"],"assets/images/payment_types/visa.png":["assets/images/payment_types/visa.png"]}
|
||||
{"assets/images/payment_types/ach.png":["assets/images/payment_types/ach.png"],"assets/images/payment_types/amex.png":["assets/images/payment_types/amex.png"],"assets/images/payment_types/carteblanche.png":["assets/images/payment_types/carteblanche.png"],"assets/images/payment_types/dinerscard.png":["assets/images/payment_types/dinerscard.png"],"assets/images/payment_types/discover.png":["assets/images/payment_types/discover.png"],"assets/images/google-icon.png":["assets/images/google-icon.png"],"assets/images/payment_types/jcb.png":["assets/images/payment_types/jcb.png"],"assets/images/payment_types/laser.png":["assets/images/payment_types/laser.png"],"assets/images/logo.png":["assets/images/logo.png"],"assets/images/payment_types/maestro.png":["assets/images/payment_types/maestro.png"],"assets/images/payment_types/mastercard.png":["assets/images/payment_types/mastercard.png"],"packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf":["packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf"],"assets/images/payment_types/other.png":["assets/images/payment_types/other.png"],"assets/images/payment_types/paypal.png":["assets/images/payment_types/paypal.png"],"assets/images/payment_types/solo.png":["assets/images/payment_types/solo.png"],"assets/images/payment_types/switch.png":["assets/images/payment_types/switch.png"],"assets/images/payment_types/unionpay.png":["assets/images/payment_types/unionpay.png"],"assets/images/payment_types/visa.png":["assets/images/payment_types/visa.png"]}
|
@ -1 +1 @@
|
||||
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/font_awesome_flutter/FontAwesomeBrands","fonts":[{"weight":400,"asset":"packages/font_awesome_flutter/lib/fonts/fa-brands-400.ttf"}]},{"family":"packages/font_awesome_flutter/FontAwesomeRegular","fonts":[{"weight":400,"asset":"packages/font_awesome_flutter/lib/fonts/fa-regular-400.ttf"}]},{"family":"packages/font_awesome_flutter/FontAwesomeSolid","fonts":[{"weight":900,"asset":"packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf"}]},{"family":"packages/material_design_icons_flutter/Material Design Icons","fonts":[{"asset":"packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf"}]}]
|
||||
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/material_design_icons_flutter/Material Design Icons","fonts":[{"asset":"packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf"}]}]
|
@ -5582,6 +5582,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
--------------------------------------------------------------------------------
|
||||
device_info
|
||||
device_info_platform_interface
|
||||
google_sign_in_platform_interface
|
||||
image_picker_platform_interface
|
||||
local_auth
|
||||
@ -5739,29 +5740,6 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
--------------------------------------------------------------------------------
|
||||
extended_image
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 zmtzawqlp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
--------------------------------------------------------------------------------
|
||||
extended_image_library
|
||||
|
||||
MIT License
|
||||
|
47
public/flutter_service_worker.js
vendored
47
public/flutter_service_worker.js
vendored
@ -3,35 +3,32 @@ const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"main.dart.js": "3a5391955070c7845ca7187ccfba0a2f",
|
||||
"/": "e65799be52f7bbcaf39d78046726b95a",
|
||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
||||
"assets/FontManifest.json": "6f5928614863ec2a06894a117283ee48",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "a68d2a28c526b3b070aefca4bac93d25",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "6a2ddad1092a0a1c326b6d0e738e682b",
|
||||
"assets/packages/font_awesome_flutter/lib/fonts/fa-regular-400.ttf": "2bca5ec802e40d3f4b60343e346cedde",
|
||||
"assets/packages/font_awesome_flutter/lib/fonts/fa-brands-400.ttf": "5a37ae808cf9f652198acde612b5328d",
|
||||
"assets/packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf": "2aa350bd2aeab88b601a593f793734c0",
|
||||
"assets/AssetManifest.json": "178db3af31496d99657040f3f3434b5a",
|
||||
"assets/NOTICES": "63bfe8452797d29679431def208599fb",
|
||||
"assets/assets/images/logo.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
||||
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/logo.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
||||
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
||||
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
||||
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
|
||||
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
||||
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
|
||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
||||
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
||||
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
|
||||
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
||||
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
||||
"assets/assets/images/google-icon.png": "0f118259ce403274f407f5e982e681c3",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628"
|
||||
"assets/NOTICES": "2ea764e7f73033bab6d857b898e4a688",
|
||||
"assets/AssetManifest.json": "ea09ed4b9b8b6c83d6896248aac7c527",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "6a2ddad1092a0a1c326b6d0e738e682b",
|
||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "a68d2a28c526b3b070aefca4bac93d25",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"main.dart.js": "40d8530505b04054904b6f285d266e65",
|
||||
"/": "e65799be52f7bbcaf39d78046726b95a",
|
||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8"
|
||||
};
|
||||
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
|
237892
public/main.dart.js
vendored
237892
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -11,6 +11,11 @@
|
||||
|
||||
<style>
|
||||
|
||||
/* fix for blurry fonts */
|
||||
flt-glass-pane {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
/* https://projects.lukehaas.me/css-loaders/ */
|
||||
.loader,
|
||||
.loader:before,
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
{!! Former::hidden('gateway_response')->id('gateway_response') !!}
|
||||
{!! Former::hidden('store_card')->id('store_card') !!}
|
||||
{!! Former::hidden('hashed_ids')->value($hashed_ids) !!}
|
||||
{!! Former::hidden('payment_hash')->value($payment_hash) !!}
|
||||
{!! Former::hidden('company_gateway_id')->value($payment_method_id) !!}
|
||||
{!! Former::hidden('payment_method_id')->value($gateway->getCompanyGatewayId()) !!}
|
||||
{!! Former::close() !!}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||
<span class="hidden mr-2 text-sm md:block">{{ ctrans('texts.per_page') }}</span>
|
||||
<select wire:model="per_page" class="py-1 text-sm form-select">
|
||||
<option>5</option>
|
||||
<option selected>10</option>
|
||||
<option>15</option>
|
||||
@ -11,50 +11,54 @@
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="mr-3">
|
||||
<input wire:click="statusChange('paid')" type="checkbox" class="form-checkbox cursor-pointer" id="paid-checkbox">
|
||||
<input wire:model="status" value="paid" type="checkbox" class="cursor-pointer form-checkbox" id="paid-checkbox">
|
||||
<label for="paid-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.status_paid') }}</label>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:click="statusChange('unpaid')" type="checkbox" class="form-checkbox cursor-pointer" id="unpaid-checkbox">
|
||||
<input wire:model="status" value="unpaid" type="checkbox" class="cursor-pointer form-checkbox" id="unpaid-checkbox">
|
||||
<label for="unpaid-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.status_unpaid') }}</label>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:click="statusChange('overdue')" type="checkbox" class="form-checkbox cursor-pointer" id="overdue-checkbox">
|
||||
<input wire:model="status" value="overdue" type="checkbox" class="cursor-pointer form-checkbox" id="overdue-checkbox">
|
||||
<label for="overdue-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.overdue') }}</label>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:model="status" value="gateway_fees" type="checkbox" class="cursor-pointer form-checkbox" id="gateway-fees-checkbox">
|
||||
<label for="gateway-fees-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.gateway_fees') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||
<table class="min-w-full shadow rounded border border-gray-200 mt-4 invoices-table">
|
||||
<div class="py-2 -my-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="inline-block min-w-full overflow-hidden align-middle rounded">
|
||||
<table class="min-w-full mt-4 border border-gray-200 rounded shadow invoices-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
|
||||
<label>
|
||||
<input type="checkbox" class="form-check form-check-parent">
|
||||
</label>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
|
||||
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||
{{ ctrans('texts.invoice_number') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
|
||||
<span role="button" wire:click="sortBy('date')" class="cursor-pointer">
|
||||
{{ ctrans('texts.invoice_date') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
|
||||
<span role="button" wire:click="sortBy('balance')" class="cursor-pointer">
|
||||
{{ ctrans('texts.balance') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
|
||||
<span role="button" wire:click="sortBy('due_date')" class="cursor-pointer">
|
||||
{{ ctrans('texts.due_date') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
|
||||
<span role="button" wire:click="sortBy('status_id')" class="cursor-pointer">
|
||||
{{ ctrans('texts.status') }}
|
||||
</span>
|
||||
@ -65,33 +69,33 @@
|
||||
<tbody>
|
||||
@forelse($invoices as $invoice)
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
|
||||
<td class="px-6 py-4 text-sm font-medium leading-5 text-gray-900 whitespace-no-wrap">
|
||||
<label>
|
||||
<input type="checkbox" class="form-check form-check-child" data-value="{{ $invoice->hashed_id }}">
|
||||
</label>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap">
|
||||
{{ $invoice->number }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap">
|
||||
{{ $invoice->formatDate($invoice->date, $invoice->client->date_format()) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap">
|
||||
{{ App\Utils\Number::formatMoney($invoice->balance, $invoice->client) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap">
|
||||
{{ $invoice->formatDate($invoice->due_date, $invoice->client->date_format()) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap">
|
||||
{!! App\Models\Invoice::badgeForStatus($invoice->status) !!}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
|
||||
<td class="flex items-center justify-end px-6 py-4 text-sm font-medium leading-5 whitespace-no-wrap">
|
||||
@if($invoice->isPayable())
|
||||
<form action="{{ route('client.invoices.bulk') }}" method="post">
|
||||
@csrf
|
||||
<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">
|
||||
<button class="px-2 py-1 mr-3 text-xs uppercase button button-primary">
|
||||
@lang('texts.pay_now')
|
||||
</button>
|
||||
</form>
|
||||
@ -103,7 +107,7 @@
|
||||
</tr>
|
||||
@empty
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||
<td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap" colspan="100%">
|
||||
{{ ctrans('texts.no_results') }}
|
||||
</td>
|
||||
</tr>
|
||||
@ -112,9 +116,9 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||
<div class="flex justify-center mt-6 mb-6 md:justify-between">
|
||||
@if($invoices->total() > 0)
|
||||
<span class="text-gray-700 text-sm hidden md:block">
|
||||
<span class="hidden text-sm text-gray-700 md:block">
|
||||
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||
</span>
|
||||
@endif
|
||||
|
@ -11,19 +11,19 @@
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="mr-3">
|
||||
<input wire:click="statusChange('draft')" type="checkbox" class="cursor-pointer form-checkbox" id="draft-checkbox">
|
||||
<input wire:model="status" value="{{ App\Models\Quote::STATUS_DRAFT }}" type="checkbox" class="cursor-pointer form-checkbox" id="draft-checkbox">
|
||||
<label for="draft-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.status_draft') }}</label>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:click="statusChange('sent')" value="sent" type="checkbox" class="cursor-pointer form-checkbox" id="sent-checkbox">
|
||||
<input wire:model="status" value="{{ App\Models\Quote::STATUS_SENT }}" value="sent" type="checkbox" class="cursor-pointer form-checkbox" id="sent-checkbox">
|
||||
<label for="sent-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.status_pending') }}</label>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:click="statusChange('approved')" value="approved" type="checkbox" class="cursor-pointer form-checkbox" id="approved-checkbox">
|
||||
<input wire:model="status" value="{{ App\Models\Quote::STATUS_APPROVED }}" value="approved" type="checkbox" class="cursor-pointer form-checkbox" id="approved-checkbox">
|
||||
<label for="approved-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.approved') }}</label>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:click="statusChange('expired')" value="expired" type="checkbox" class="cursor-pointer form-checkbox" id="expired-checkbox">
|
||||
<input wire:model="status" value="{{ App\Models\Quote::STATUS_EXPIRED }}" value="expired" type="checkbox" class="cursor-pointer form-checkbox" id="expired-checkbox">
|
||||
<label for="expired-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.expired') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,9 +13,7 @@
|
||||
@section('body')
|
||||
<form action="{{ route('client.payments.response') }}" method="post" id="server_response">
|
||||
@csrf
|
||||
@foreach($invoices as $invoice)
|
||||
<input type="hidden" name="hashed_ids[]" value="{{ $invoice->hashed_id }}">
|
||||
@endforeach
|
||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->id }}">
|
||||
<input type="hidden" name="payment_method_id" value="1">
|
||||
<input type="hidden" name="gateway_response" id="gateway_response">
|
||||
@ -23,7 +21,7 @@
|
||||
<input type="hidden" name="dataDescriptor" id="dataDescriptor" />
|
||||
<input type="hidden" name="token" id="token" />
|
||||
<input type="hidden" name="store_card" id="store_card" />
|
||||
<input type="hidden" name="amount_with_fee" id="amount_with_fee" value="{{ $amount_with_fee }}" />
|
||||
<input type="hidden" name="amount_with_fee" id="amount_with_fee" value="{{ $total['amount_with_fee'] }}" />
|
||||
</form>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
@ -40,10 +38,22 @@
|
||||
<dl>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.amount') }}
|
||||
{{ ctrans('texts.subtotal') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($amount_with_fee, $client) }}
|
||||
{{ App\Utils\Number::formatMoney($total['invoice_totals'], $client) }}
|
||||
</dd>
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.gateway_fees') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($total['fee_total'], $client) }}
|
||||
</dd>
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.total') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($total['amount_with_fee'], $client) }}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
@ -67,11 +77,23 @@
|
||||
<div>
|
||||
<dl>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.totals') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($total['invoice_totals'], $client) }}
|
||||
</dd>
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.gateway_fees') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($total['fee_total'], $client) }}
|
||||
</dd>
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.amount') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($amount_with_fee, $client) }}
|
||||
{{ App\Utils\Number::formatMoney($total['amount_with_fee'], $client) }}
|
||||
</dd>
|
||||
</div>
|
||||
@foreach($tokens as $token)
|
||||
|
@ -11,9 +11,8 @@
|
||||
@csrf
|
||||
<input type="hidden" name="gateway_response">
|
||||
<input type="hidden" name="store_card">
|
||||
@foreach($invoices as $invoice)
|
||||
<input type="hidden" name="hashed_ids[]" value="{{ $invoice->hashed_id }}">
|
||||
@endforeach
|
||||
<input type="hidden" name="payment_hash" value="{{$payment_hash}}">
|
||||
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
|
||||
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
|
||||
</form>
|
||||
|
@ -10,13 +10,10 @@
|
||||
@section('body')
|
||||
<form action="{{ route('client.payments.process') }}" method="post" id="payment-form">
|
||||
@csrf
|
||||
@foreach($invoices as $invoice)
|
||||
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
||||
@endforeach
|
||||
<input type="hidden" name="company_gateway_id" id="company_gateway_id">
|
||||
<input type="hidden" name="payment_method_id" id="payment_method_id">
|
||||
<input type="hidden" name="signature">
|
||||
</form>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||
@ -63,7 +60,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach($invoices as $invoice)
|
||||
@foreach($invoices as $key => $invoice)
|
||||
<input type="hidden" name="payable_invoices[{{$key}}][invoice_id]" value="{{ $invoice->hashed_id }}">
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg mb-4">
|
||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
@ -104,7 +102,7 @@
|
||||
@elseif($invoice->public_notes)
|
||||
{{ $invoice->public_notes }}
|
||||
@else
|
||||
{{ $invoice->invoice_date}}
|
||||
{{ $invoice->date}}
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
@ -113,7 +111,8 @@
|
||||
{{ ctrans('texts.amount') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||
<!-- App\Utils\Number::formatMoney($invoice->amount, $invoice->client) -->
|
||||
<input type="text" name="payable_invoices[{{$key}}][amount]" value="{{ $invoice->partial > 0 ? $invoice->partial : $invoice->balance }}">
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@ -123,7 +122,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@include('portal.ninja2020.invoices.includes.terms')
|
||||
@include('portal.ninja2020.invoices.includes.signature')
|
||||
@endsection
|
||||
|
@ -34,13 +34,13 @@
|
||||
@include('setup._issues')
|
||||
@else
|
||||
|
||||
@if(!$check['npm_status'])
|
||||
@if(isset($check['npm_status']) && !$check['npm_status'])
|
||||
<div class="alert alert-success mt-4">
|
||||
<p>NPM Version => {{$check['npm_status']}}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!$check['node_status'])
|
||||
@if(isset($check['node_status']) && !$check['node_status'])
|
||||
<div class="alert alert-success mt-4">
|
||||
<p>Node Version => {{$check['node_status']}}</p>
|
||||
</div>
|
||||
|
@ -314,7 +314,41 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(11, $company_gateway->calcGatewayFee(10));
|
||||
$this->assertEquals(11, $company_gateway->calcGatewayFee(10, true));
|
||||
}
|
||||
|
||||
public function testFeesAndLimitsFeePercentAndAmountAndTaxCalcuationInclusiveTaxes()
|
||||
{
|
||||
//{"1":{"min_limit":1,"max_limit":1000000,"fee_amount":10,"fee_percent":2,"fee_tax_name1":"","fee_tax_name2":"","fee_tax_name3":"","fee_tax_rate1":0,"fee_tax_rate2":0,"fee_tax_rate3":0,"fee_cap":10,"adjust_fee_percent":true}}
|
||||
$fee = new FeesAndLimits;
|
||||
$fee->fee_amount = 10;
|
||||
// $fee->fee_percent = 2;
|
||||
$fee->fee_tax_name1 = 'GST';
|
||||
$fee->fee_tax_rate1 = '10.0';
|
||||
|
||||
$fee_arr[1] = (array)$fee;
|
||||
|
||||
$data = [
|
||||
'config' => 'random config',
|
||||
'gateway_key' => '3b6621f970ab18887c4f6dca78d3f8bb',
|
||||
'fees_and_limits' => $fee_arr,
|
||||
];
|
||||
|
||||
/* POST */
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token
|
||||
])->post('/api/v1/company_gateways', $data);
|
||||
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
$id = $this->decodePrimaryKey($arr['data']['id']);
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(10, $company_gateway->calcGatewayFee(10));
|
||||
}
|
||||
|
||||
public function testFeesAndLimitsFeePercentAndAmountAndDoubleTaxCalcuation()
|
||||
@ -351,7 +385,7 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(12, $company_gateway->calcGatewayFee(10));
|
||||
$this->assertEquals(12, $company_gateway->calcGatewayFee(10,true));
|
||||
}
|
||||
|
||||
|
||||
@ -389,6 +423,6 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(1, $company_gateway->calcGatewayFee(10));
|
||||
$this->assertEquals(1.2, $company_gateway->calcGatewayFee(10,true));
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ class CompanyGatewayTest extends TestCase
|
||||
$data[1]['fee_tax_rate2'] = '';
|
||||
$data[1]['fee_tax_name3'] = '';
|
||||
$data[1]['fee_tax_rate3'] = 0;
|
||||
$data[1]['fee_cap'] = 0;
|
||||
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $this->company->id;
|
||||
@ -107,4 +108,125 @@ class CompanyGatewayTest extends TestCase
|
||||
|
||||
return $passes;
|
||||
}
|
||||
|
||||
public function testFeesAreAppendedToInvoice() //after refactor this may be redundant
|
||||
{
|
||||
|
||||
$data = [];
|
||||
$data[1]['min_limit'] = -1;
|
||||
$data[1]['max_limit'] = -1;
|
||||
$data[1]['fee_amount'] = 1.00;
|
||||
$data[1]['fee_percent'] = 0.000;
|
||||
$data[1]['fee_tax_name1'] = '';
|
||||
$data[1]['fee_tax_rate1'] = 0;
|
||||
$data[1]['fee_tax_name2'] = '';
|
||||
$data[1]['fee_tax_rate2'] = 0;
|
||||
$data[1]['fee_tax_name3'] = '';
|
||||
$data[1]['fee_tax_rate3'] = 0;
|
||||
$data[1]['fee_cap'] = 0;
|
||||
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $this->company->id;
|
||||
$cg->user_id = $this->user->id;
|
||||
$cg->gateway_key = 'd14dd26a37cecc30fdd65700bfb55b23';
|
||||
$cg->require_cvv = true;
|
||||
$cg->show_billing_address = true;
|
||||
$cg->show_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.stripe'));
|
||||
$cg->fees_and_limits = $data;
|
||||
$cg->save();
|
||||
|
||||
$balance = $this->invoice->balance;
|
||||
|
||||
$this->invoice = $this->invoice->service()->addGatewayFee($cg, $this->invoice->balance)->save();
|
||||
$this->invoice = $this->invoice->calc()->getInvoice();
|
||||
|
||||
$items = $this->invoice->line_items;
|
||||
|
||||
$this->assertEquals(($balance+1), $this->invoice->balance);
|
||||
}
|
||||
|
||||
public function testProRataGatewayFees()
|
||||
{
|
||||
|
||||
$data = [];
|
||||
$data[1]['min_limit'] = -1;
|
||||
$data[1]['max_limit'] = -1;
|
||||
$data[1]['fee_amount'] = 1.00;
|
||||
$data[1]['fee_percent'] = 2;
|
||||
$data[1]['fee_tax_name1'] = 'GST';
|
||||
$data[1]['fee_tax_rate1'] = 10;
|
||||
$data[1]['fee_tax_name2'] = 'GST';
|
||||
$data[1]['fee_tax_rate2'] = 10;
|
||||
$data[1]['fee_tax_name3'] = 'GST';
|
||||
$data[1]['fee_tax_rate3'] = 10;
|
||||
$data[1]['fee_cap'] = 0;
|
||||
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $this->company->id;
|
||||
$cg->user_id = $this->user->id;
|
||||
$cg->gateway_key = 'd14dd26a37cecc30fdd65700bfb55b23';
|
||||
$cg->require_cvv = true;
|
||||
$cg->show_billing_address = true;
|
||||
$cg->show_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.stripe'));
|
||||
$cg->fees_and_limits = $data;
|
||||
$cg->save();
|
||||
|
||||
|
||||
$total = 10.93;
|
||||
$total_invoice_count = 5;
|
||||
$total_gateway_fee = round($cg->calcGatewayFee($total,true),2);
|
||||
|
||||
$this->assertEquals(1.58, $total_gateway_fee);
|
||||
|
||||
/*simple pro rata*/
|
||||
$fees_and_limits = $cg->getFeesAndLimits();
|
||||
|
||||
|
||||
/*Calculate all subcomponents of the fee*/
|
||||
|
||||
// $fee_component_amount = $fees_and_limits->fee_amount ?: 0;
|
||||
// $fee_component_percent = $fees_and_limits->fee_percent ? ($total * $fees_and_limits->fee_percent / 100) : 0;
|
||||
|
||||
// $combined_fee_component = $fee_component_amount + $fee_component_percent;
|
||||
|
||||
// $fee_component_tax_name1 = $fees_and_limits->fee_tax_name1 ?: '';
|
||||
// $fee_component_tax_rate1 = $fees_and_limits->fee_tax_rate1 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate1 / 100) : 0;
|
||||
|
||||
// $fee_component_tax_name2 = $fees_and_limits->fee_tax_name2 ?: '';
|
||||
// $fee_component_tax_rate2 = $fees_and_limits->fee_tax_rate2 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate2 / 100) : 0;
|
||||
|
||||
// $fee_component_tax_name3 = $fees_and_limits->fee_tax_name3 ?: '';
|
||||
// $fee_component_tax_rate3 = $fees_and_limits->fee_tax_rate3 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate3 / 100) : 0;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// $pro_rata_fee = round($total_gateway_fee / $total_invoice_count,2);
|
||||
|
||||
|
||||
// while($pro_rata_fee * $total_invoice_count != $total_gateway_fee) {
|
||||
|
||||
// //nudge one pro rata fee until we get the desired amount
|
||||
// $sub_total_fees = ($pro_rata_fee*($total_invoice_count--));
|
||||
|
||||
// //work out if we have to nudge up or down
|
||||
|
||||
// if($pro_rata_fee*$total_invoice_count > $total_gateway_fee) {
|
||||
// //nudge DOWN
|
||||
// $pro_rata_fee - 0.01; //this will break if the currency doesn't have decimals
|
||||
// }
|
||||
// else {
|
||||
// //nudge UP
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// $this->assertEquals(1.56, $pro_rata_fee*$total_invoice_count);
|
||||
|
||||
}
|
||||
}
|
@ -50,9 +50,9 @@ class ExampleIntegrationTest extends TestCase
|
||||
->design($design)
|
||||
->build();
|
||||
|
||||
exec('echo "" > storage/logs/laravel.log');
|
||||
// exec('echo "" > storage/logs/laravel.log');
|
||||
|
||||
info($maker->getCompiledHTML(true));
|
||||
// info($maker->getCompiledHTML(true));
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
@ -360,9 +360,9 @@ class PdfMakerTest extends TestCase
|
||||
->design($design)
|
||||
->build();
|
||||
|
||||
exec('echo "" > storage/logs/laravel.log');
|
||||
// exec('echo "" > storage/logs/laravel.log');
|
||||
|
||||
info($maker->getCompiledHTML(true));
|
||||
// info($maker->getCompiledHTML(true));
|
||||
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ class CompanyLedgerTest extends TestCase
|
||||
|
||||
$payment_ledger = $payment->company_ledger->sortByDesc('id')->first();
|
||||
|
||||
info($payment->client->balance);
|
||||
//info($payment->client->balance);
|
||||
|
||||
$this->assertEquals($payment->client->balance, $payment_ledger->balance);
|
||||
$this->assertEquals($payment->client->paid_to_date, 10);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Models\Currency;
|
||||
use App\Utils\Number;
|
||||
use Tests\TestCase;
|
||||
|
||||
@ -31,4 +32,25 @@ class NumberTest extends TestCase
|
||||
|
||||
$this->assertEquals(2.15, $rounded);
|
||||
}
|
||||
|
||||
public function testParsingFloats()
|
||||
{
|
||||
|
||||
Currency::all()->each(function ($currency){
|
||||
|
||||
$amount = 123456789.12;
|
||||
|
||||
$formatted_amount = Number::formatValue($amount, $currency);
|
||||
|
||||
$float_amount = Number::parseFloat($formatted_amount);
|
||||
|
||||
if($currency->precision == 0){
|
||||
$this->assertEquals(123456789, $float_amount);
|
||||
}
|
||||
else
|
||||
$this->assertEquals($amount, $float_amount);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user