1
0
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:
Benjamin Beganović 2020-09-04 13:20:39 +02:00
commit 1a66f1835f
61 changed files with 119929 additions and 119570 deletions

View File

@ -1,5 +1,5 @@
APP_NAME="Invoice Ninja"
APP_ENV=local
APP_ENV=production
APP_KEY=
APP_DEBUG=false

View File

@ -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',

View File

@ -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);
}

View File

@ -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'))

View File

@ -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);

View File

@ -31,7 +31,6 @@ class MigrationController extends BaseController
parent::__construct();
}
/**
*
* Purge Company

View File

@ -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,
]);

View File

@ -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

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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'])) {

View File

@ -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) {

View File

@ -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);

View File

@ -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);
// }
}
}

View File

@ -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()

View File

@ -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

View File

@ -76,6 +76,7 @@ class Payment extends BaseModel
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
'is_deleted' => 'bool',
'meta' => 'object',
];
protected $with = [

View 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;
}
}

View File

@ -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();

View File

@ -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'],
];
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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');
}
});
}
}

View File

@ -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');
}
});
}
}

View File

@ -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) {}
}

View File

@ -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'];

View File

@ -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()));

View File

@ -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()));

View File

@ -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);
}
/**

View 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;
}
}

View File

@ -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

View File

@ -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;
}
/**

View File

@ -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;
}

View File

@ -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;

View File

@ -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()

View File

@ -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;
}

View File

@ -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(),
];
}

View File

@ -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
*

View File

@ -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(),
];
}

View File

@ -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');
});
}

View File

@ -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);
}

View File

@ -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"]}

View File

@ -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"}]}]

View File

@ -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

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,11 @@
<style>
/* fix for blurry fonts */
flt-glass-pane {
image-rendering: pixelated;
}
/* https://projects.lukehaas.me/css-loaders/ */
.loader,
.loader:before,

View File

@ -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() !!}

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
});
}
}