mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 13:12:50 +01:00
commit
544ae8ad63
2
.gitignore
vendored
2
.gitignore
vendored
@ -17,7 +17,7 @@ local_version.txt
|
||||
|
||||
/resources/assets/bower
|
||||
/public/logo
|
||||
|
||||
/storage/*
|
||||
.env.dusk.local
|
||||
/public/vendors/*
|
||||
*.log
|
||||
|
@ -1 +1 @@
|
||||
5.0.31
|
||||
5.0.32
|
@ -19,7 +19,6 @@ use App\Exceptions\ResourceDependencyMissing;
|
||||
use App\Exceptions\ResourceNotAvailableForMigration;
|
||||
use App\Jobs\Util\Import;
|
||||
use App\Jobs\Util\StartMigration;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\MigrationFailed;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
@ -85,7 +84,6 @@ class ImportMigrations extends Command
|
||||
|
||||
foreach ($directory as $file) {
|
||||
if ($file->getExtension() === 'zip') {
|
||||
|
||||
$user = $this->getUser();
|
||||
$company = $this->getUser()->companies()->first();
|
||||
|
||||
@ -99,24 +97,24 @@ class ImportMigrations extends Command
|
||||
throw new ProcessingMigrationArchiveFailed('Processing migration archive failed. Migration file is possibly corrupted.');
|
||||
}
|
||||
|
||||
$filename = pathinfo($file->getRealPath(), PATHINFO_FILENAME);
|
||||
$filename = pathinfo($file->getRealPath(), PATHINFO_FILENAME);
|
||||
|
||||
$zip->extractTo(public_path("storage/migrations/{$filename}"));
|
||||
$zip->close();
|
||||
$zip->extractTo(public_path("storage/migrations/{$filename}"));
|
||||
$zip->close();
|
||||
|
||||
$import_file = public_path("storage/migrations/$filename/migration.json");
|
||||
$import_file = public_path("storage/migrations/$filename/migration.json");
|
||||
|
||||
Import::dispatch($import_file, $this->getUser()->companies()->first(), $this->getUser());
|
||||
// StartMigration::dispatch($file->getRealPath(), $this->getUser(), $this->getUser()->companies()->first());
|
||||
Import::dispatch($import_file, $this->getUser()->companies()->first(), $this->getUser());
|
||||
// StartMigration::dispatch($file->getRealPath(), $this->getUser(), $this->getUser()->companies()->first());
|
||||
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
|
||||
\Mail::to($this->user)->send(new MigrationFailed($e, $e->getMessage()));
|
||||
|
||||
if (app()->environment() !== 'production') {
|
||||
info($e->getMessage());
|
||||
}
|
||||
}
|
||||
catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
|
||||
\Mail::to($this->user)->send(new MigrationFailed($e, $e->getMessage()));
|
||||
|
||||
if (app()->environment() !== 'production') {
|
||||
info($e->getMessage());
|
||||
}
|
||||
|
||||
}}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
|
@ -230,14 +230,16 @@ class InvoiceSumInclusive
|
||||
private function setCalculatedAttributes()
|
||||
{
|
||||
/* If amount != balance then some money has been paid on the invoice, need to subtract this difference from the total to set the new balance */
|
||||
if ($this->invoice->amount != $this->invoice->balance) {
|
||||
$paid_to_date = $this->invoice->amount - $this->invoice->balance;
|
||||
if ($this->invoice->status_id != Invoice::STATUS_DRAFT) {
|
||||
if ($this->invoice->amount != $this->invoice->balance) {
|
||||
$paid_to_date = $this->invoice->amount - $this->invoice->balance;
|
||||
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision) - $paid_to_date;
|
||||
} else {
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision) - $paid_to_date;
|
||||
} else {
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Set new calculated total */
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
|
||||
|
@ -102,7 +102,6 @@ class InvoiceController extends BaseController
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
@ -691,7 +690,7 @@ class InvoiceController extends BaseController
|
||||
break;
|
||||
case 'delete':
|
||||
//need to make sure the invoice is cancelled first!!
|
||||
$invoice->service()->handleCancellation()->save();
|
||||
//$invoice->service()->handleCancellation()->save();
|
||||
|
||||
$this->invoice_repo->delete($invoice);
|
||||
|
||||
|
@ -23,14 +23,17 @@ class PaymentWebhookController extends Controller
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
public function __invoke(PaymentWebhookRequest $request, string $company_key, string $gateway_key)
|
||||
public function __invoke(PaymentWebhookRequest $request, string $company_key = null, string $gateway_key = null)
|
||||
{
|
||||
$transaction_reference = $this->getTransactionReference($request->all());
|
||||
$transaction_reference = $this->getTransactionReference($request->all(), $request);
|
||||
|
||||
$payment = Payment::where('transaction_reference', $transaction_reference)->first();
|
||||
|
||||
if (is_null($payment)) {
|
||||
return response([], 404); /* Record event, throw an exception.. */
|
||||
return response([
|
||||
'message' => 'Sorry, we couldn\'t find requested payment.',
|
||||
'status_code' => 404,
|
||||
], 404); /* Record event, throw an exception.. */
|
||||
}
|
||||
|
||||
return $request
|
||||
@ -40,12 +43,16 @@ class PaymentWebhookController extends Controller
|
||||
->processWebhookRequest($request->all(), $request->company(), $request->companyGateway(), $payment);
|
||||
}
|
||||
|
||||
public function getTransactionReference(array $data)
|
||||
public function getTransactionReference(array $data, PaymentWebhookRequest $request)
|
||||
{
|
||||
$flatten = Arr::dot($data);
|
||||
|
||||
if (isset($flatten['data.object.id'])) {
|
||||
return $flatten['data.object.id']; // Request from Stripe
|
||||
return $flatten['data.object.id']; // stripe.com
|
||||
}
|
||||
|
||||
if ($request->has('cko-session-id')) {
|
||||
// checkout.com
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ class SetupController extends Controller
|
||||
if (count($response_array) == 0) {
|
||||
return response([], 200);
|
||||
} else {
|
||||
return response()->json($response_array, 200);
|
||||
return response()->json(['message' => $response_array[0]], 400);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
info(['message' => $e->getMessage(), 'action' => 'SetupController::checkMail()']);
|
||||
|
@ -13,6 +13,7 @@ namespace App\Http;
|
||||
|
||||
use App\Http\Middleware\ApiSecretCheck;
|
||||
use App\Http\Middleware\Authenticate;
|
||||
use App\Http\Middleware\CheckClientExistence;
|
||||
use App\Http\Middleware\CheckForMaintenanceMode;
|
||||
use App\Http\Middleware\ClientPortalEnabled;
|
||||
use App\Http\Middleware\ContactKeyLogin;
|
||||
@ -155,5 +156,6 @@ class Kernel extends HttpKernel
|
||||
'shop_token_auth' => ShopTokenAuth::class,
|
||||
'phantom_secret' => PhantomSecret::class,
|
||||
'contact_key_login' => ContactKeyLogin::class,
|
||||
'check_client_existence' => CheckClientExistence::class,
|
||||
];
|
||||
}
|
||||
|
54
app/Http/Middleware/CheckClientExistence.php
Normal file
54
app/Http/Middleware/CheckClientExistence.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?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\Http\Middleware;
|
||||
|
||||
use App\Models\ClientContact;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CheckClientExistence
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$multiple_contacts = ClientContact::query()
|
||||
->where('email', auth('contact')->user()->email)
|
||||
->whereNotNull('email')
|
||||
->distinct('company_id')
|
||||
->whereHas('client', function ($query) {
|
||||
return $query->whereNull('deleted_at');
|
||||
})
|
||||
->get();
|
||||
|
||||
if (count($multiple_contacts) == 0) {
|
||||
Auth::logout();
|
||||
|
||||
return redirect()->route('client.login');
|
||||
}
|
||||
|
||||
if (count($multiple_contacts) == 1) {
|
||||
Auth::guard('contact')->login($multiple_contacts[0], true);
|
||||
}
|
||||
|
||||
session()->put('multiple_contacts', $multiple_contacts);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@
|
||||
|
||||
namespace App\Http\ViewComposers;
|
||||
|
||||
use App\Models\ClientContact;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\TranslationHelper;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
@ -55,7 +54,7 @@ class PortalComposer
|
||||
$data['settings'] = auth()->user()->client->getMergedSettings();
|
||||
$data['currencies'] = TranslationHelper::getCurrencies();
|
||||
|
||||
$data['multiple_contacts'] = ClientContact::where('email', auth('contact')->user()->email)->whereNotNull('email')->distinct('company_id')->get();
|
||||
$data['multiple_contacts'] = session()->get('multiple_contacts');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -146,7 +146,8 @@ class CreateEntityPdf implements ShouldQueue
|
||||
->build();
|
||||
|
||||
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
|
||||
// Storage::makeDirectory($path, 0775);
|
||||
//info("make dir => {$path}");
|
||||
//Storage::makeDirectory($path, 0775);
|
||||
|
||||
$pdf = null;
|
||||
|
||||
|
@ -37,7 +37,9 @@ class UploadAvatar implements ShouldQueue
|
||||
{
|
||||
|
||||
//make dir
|
||||
Storage::makeDirectory($this->directory, 0775);
|
||||
// info("avatar dir creation => ". $this->directory);
|
||||
|
||||
// Storage::makeDirectory($this->directory, 0775);
|
||||
|
||||
$tmp_file = sha1(time()).'.png';
|
||||
|
||||
|
@ -45,7 +45,7 @@ class BouncedEmail extends Mailable implements ShouldQueue
|
||||
$subject = ctrans("texts.notification_{$entity_type}_bounced_subject", ['invoice' => $invoice->number]);
|
||||
|
||||
return
|
||||
$this->from(config('mail.from.name'), config('mail.from.address'))
|
||||
$this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->text()
|
||||
->subject($subject);
|
||||
|
||||
|
@ -31,7 +31,8 @@ class DownloadInvoices extends Mailable
|
||||
public function build()
|
||||
{
|
||||
|
||||
return $this->from(config('mail.from.name'), config('mail.from.address'))
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
|
||||
->subject(ctrans('texts.download_files'))
|
||||
->markdown(
|
||||
'email.admin.download_files',
|
||||
|
@ -83,7 +83,6 @@ class BaseEmailEngine implements EngineInterface
|
||||
|
||||
public function setAttachments($attachments)
|
||||
{
|
||||
|
||||
$this->attachments = array_merge($this->getAttachments(), $attachments);
|
||||
|
||||
return $this;
|
||||
|
@ -28,7 +28,8 @@ class ExistingMigration extends Mailable
|
||||
public function build()
|
||||
{
|
||||
|
||||
return $this->from(config('mail.from.name'), config('mail.from.address'))
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
|
||||
->view('email.migration.existing');
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class MigrationCompleted extends Mailable
|
||||
{
|
||||
$data['settings'] = auth()->user()->company()->settings;
|
||||
|
||||
return $this->from(config('mail.from.name'), config('mail.from.address'))
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->view('email.migration.completed', $data);
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ class MigrationFailed extends Mailable
|
||||
public function build()
|
||||
{
|
||||
|
||||
return $this->from(config('mail.from.name'), config('mail.from.address'))
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
|
||||
->view('email.migration.failed');
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class CreditCard
|
||||
$checkout_response = $this->checkout->payment_hash->data->server_response;
|
||||
|
||||
$method = new TokenSource(
|
||||
$checkout_response->cardToken
|
||||
$checkout_response->token
|
||||
);
|
||||
|
||||
return $this->completePayment($method, $request);
|
||||
|
@ -60,7 +60,7 @@ trait Utilities
|
||||
'payment_method' => $_payment->source['id'],
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($_payment->source['scheme'])),
|
||||
'amount' => $this->checkout->payment_hash->data->raw_value,
|
||||
'transaction_reference' => $_payment->reference,
|
||||
'transaction_reference' => $_payment->id,
|
||||
];
|
||||
|
||||
$payment = $this->checkout->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
|
||||
@ -104,9 +104,10 @@ trait Utilities
|
||||
private function processPendingPayment(Payment $_payment)
|
||||
{
|
||||
$data = [
|
||||
'payment_method' => $_payment->source['id'],
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($_payment->source['scheme'])),
|
||||
'payment_method' => $_payment->source->id,
|
||||
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
||||
'amount' => $this->checkout->payment_hash->data->value,
|
||||
'transaction_reference' => $_payment->id,
|
||||
];
|
||||
|
||||
$payment = $this->checkout->createPayment($data, \App\Models\Payment::STATUS_PENDING);
|
||||
|
@ -69,13 +69,38 @@ class InvoiceRepository extends BaseRepository
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
$invoice->service()->markDeleted()->handleCancellation()->save();
|
||||
// $invoice->service()->markDeleted()->handleCancellation()->save();
|
||||
$invoice = $invoice->service()->markDeleted()->save();
|
||||
|
||||
parent::delete($invoice);
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the restoration on a deleted invoice.
|
||||
*
|
||||
* @param [type] $invoice [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function restore($invoice) :Invoice
|
||||
{
|
||||
//if we have just archived, only perform a soft restore
|
||||
if(!$invoice->is_deleted) {
|
||||
|
||||
parent::restore($invoice);
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
// reversed delete invoice actions
|
||||
$invoice = $invoice->service()->handleRestore()->save();
|
||||
|
||||
parent::restore($invoice);
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
public function reverse()
|
||||
{
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ class MarkSent
|
||||
->service()
|
||||
->setStatus(Credit::STATUS_SENT)
|
||||
->applyNumber()
|
||||
->adjustBalance($this->credit->amount)
|
||||
->save();
|
||||
|
||||
|
||||
|
@ -84,7 +84,7 @@ class GenerateDeliveryNote
|
||||
->design($template)
|
||||
->build();
|
||||
|
||||
Storage::makeDirectory($this->invoice->client->invoice_filepath(), 0775);
|
||||
// Storage::makeDirectory($this->invoice->client->invoice_filepath(), 0775);
|
||||
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML());
|
||||
|
||||
|
115
app/Services/Invoice/HandleRestore.php
Normal file
115
app/Services/Invoice/HandleRestore.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?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\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class HandleRestore extends AbstractService
|
||||
{
|
||||
|
||||
private $invoice;
|
||||
|
||||
private $payment_total = 0;
|
||||
|
||||
public function __construct(Invoice $invoice)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
if(!$this->invoice->is_deleted)
|
||||
return $this->invoice;
|
||||
|
||||
//determine whether we need to un-delete payments OR just modify the payment amount /applied balances.
|
||||
|
||||
foreach($this->invoice->payments as $payment)
|
||||
{
|
||||
//restore the payment record
|
||||
$payment->restore();
|
||||
|
||||
//determine the paymentable amount before paymentable restoration
|
||||
$pre_restore_amount = $payment->paymentables()
|
||||
->where('paymentable_type', '=', 'invoices')
|
||||
->sum(\DB::raw('amount'));
|
||||
|
||||
//restore the paymentables
|
||||
$payment->paymentables()
|
||||
->where('paymentable_type', '=', 'invoices')
|
||||
->where('paymentable_id', $this->invoice->id)
|
||||
->restore();
|
||||
|
||||
//determine the post restore paymentable amount (we need to increment the payment amount by the difference between pre and post)
|
||||
$payment_amount = $payment->paymentables()
|
||||
->where('paymentable_type', '=', 'invoices')
|
||||
->sum(\DB::raw('amount'));
|
||||
|
||||
info($payment->amount . " == " . $payment_amount);
|
||||
|
||||
if($payment->amount == $payment_amount) {
|
||||
|
||||
$payment->is_deleted = false;
|
||||
$payment->save();
|
||||
|
||||
$this->payment_total += $payment_amount;
|
||||
}
|
||||
else {
|
||||
|
||||
$payment->is_deleted = false;
|
||||
$payment->amount += ($payment_amount - $pre_restore_amount);
|
||||
$payment->applied += ($payment_amount - $pre_restore_amount);
|
||||
$payment->save();
|
||||
|
||||
$this->payment_total += ($payment_amount - $pre_restore_amount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//adjust ledger balance
|
||||
$this->invoice->ledger()->updateInvoiceBalance($this->invoice->balance, 'Restored invoice {$this->invoice->number}')->save();
|
||||
|
||||
//adjust paid to dates
|
||||
$this->invoice->client->service()->updatePaidToDate($this->payment_total)->save();
|
||||
|
||||
$this->invoice->client->service()->updateBalance($this->invoice->balance)->save();
|
||||
|
||||
$this->invoice->ledger()->updatePaymentBalance($this->payment_total, 'Restored payment for invoice {$this->invoice->number}')->save();
|
||||
|
||||
$this->windBackInvoiceNumber();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
|
||||
private function windBackInvoiceNumber()
|
||||
{
|
||||
|
||||
$findme = '_' . ctrans('texts.deleted');
|
||||
|
||||
$pos = strpos($this->invoice->number, $findme);
|
||||
|
||||
$new_invoice_number = substr($this->invoice->number, 0, $pos);
|
||||
|
||||
try {
|
||||
$this->invoice->number = $new_invoice_number;
|
||||
$this->invoice->save();
|
||||
}
|
||||
catch(\Exception $e){
|
||||
info("I could not wind back the invoice number");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +164,13 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function handleRestore()
|
||||
{
|
||||
$this->invoice = (new HandleRestore($this->invoice))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function reverseCancellation()
|
||||
{
|
||||
$this->invoice = (new HandleCancellation($this->invoice))->reverse();
|
||||
|
@ -14,6 +14,7 @@ namespace App\Services\Invoice;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MarkInvoiceDeleted extends AbstractService
|
||||
{
|
||||
@ -21,6 +22,10 @@ class MarkInvoiceDeleted extends AbstractService
|
||||
|
||||
private $invoice;
|
||||
|
||||
private $adjustment_amount = 0;
|
||||
|
||||
private $total_payments = 0;
|
||||
|
||||
public function __construct(Invoice $invoice)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
@ -28,7 +33,94 @@ class MarkInvoiceDeleted extends AbstractService
|
||||
|
||||
public function run()
|
||||
{
|
||||
if($this->invoice->is_deleted)
|
||||
return $this->invoice;
|
||||
|
||||
// if(in_array($this->invoice->status_id, ['currencies', 'industries', 'languages', 'countries', 'banks']))
|
||||
// return $this->
|
||||
|
||||
$this->cleanup()
|
||||
->setAdjustmentAmount()
|
||||
->deletePaymentables()
|
||||
->adjustPayments()
|
||||
->adjustPaidToDate()
|
||||
->adjustBalance()
|
||||
->adjustLedger();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
private function adjustLedger()
|
||||
{
|
||||
$this->invoice->ledger()->updatePaymentBalance($this->adjustment_amount * -1);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function adjustPaidToDate()
|
||||
{
|
||||
$this->invoice->client->service()->updatePaidToDate($this->adjustment_amount * -1)->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function adjustBalance()
|
||||
{
|
||||
$this->invoice->client->service()->updateBalance($this->invoice->balance * -1)->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function adjustPayments()
|
||||
{
|
||||
//if total payments = adjustment amount - that means we need to delete the payments as well.
|
||||
|
||||
if($this->adjustment_amount == $this->total_payments) {
|
||||
|
||||
$this->invoice->payments()->update(['payments.deleted_at' => now(), 'payments.is_deleted' => true]);
|
||||
|
||||
}
|
||||
else {
|
||||
//adjust payments down by the amount applied to the invoice payment.
|
||||
|
||||
$this->invoice->payments->each(function ($payment){
|
||||
|
||||
$payment_adjustment = $payment->paymentables
|
||||
->where('paymentable_type', '=', 'invoices')
|
||||
->where('paymentable_id', $this->invoice->id)
|
||||
->sum(DB::raw('amount'));
|
||||
|
||||
$payment->amount -= $payment_adjustment;
|
||||
$payment->applied -= $payment_adjustment;
|
||||
$payment->save();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setAdjustmentAmount()
|
||||
{
|
||||
|
||||
foreach ($this->invoice->payments as $payment) {
|
||||
$this->adjustment_amount += $payment->paymentables
|
||||
->where('paymentable_type', '=', 'invoices')
|
||||
->where('paymentable_id', $this->invoice->id)
|
||||
->sum(DB::raw('amount'));
|
||||
}
|
||||
|
||||
|
||||
$this->total_payments = $this->invoice->payments->sum('amount');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function cleanup()
|
||||
{
|
||||
|
||||
$check = false;
|
||||
|
||||
$x=0;
|
||||
|
||||
do {
|
||||
@ -43,10 +135,10 @@ class MarkInvoiceDeleted extends AbstractService
|
||||
$this->invoice->tasks()->update(['invoice_id' => null]);
|
||||
$this->invoice->expenses()->update(['invoice_id' => null]);
|
||||
|
||||
return $this->invoice;
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function calcNumber($x)
|
||||
{
|
||||
if ($x==0) {
|
||||
@ -57,4 +149,19 @@ class MarkInvoiceDeleted extends AbstractService
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
|
||||
private function deletePaymentables()
|
||||
{
|
||||
|
||||
$this->invoice->payments->each(function ($payment){
|
||||
$payment->paymentables()
|
||||
->where('paymentable_type', '=', 'invoices')
|
||||
->where('paymentable_id', $this->invoice->id)
|
||||
->update(['deleted_at' => now()]);
|
||||
});
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,6 @@ class RefundPayment
|
||||
{
|
||||
if ($this->refund_data['gateway_refund'] !== false && $this->total_refund > 0) {
|
||||
if ($this->payment->company_gateway) {
|
||||
|
||||
$response = $this->payment->company_gateway->driver($this->payment->client)->refund($this->payment, $this->total_refund);
|
||||
|
||||
$this->payment->refunded += $this->total_refund;
|
||||
@ -84,7 +83,6 @@ class RefundPayment
|
||||
$this->payment->save();
|
||||
throw new PaymentRefundFailed();
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
$this->payment->refunded += $this->total_refund;
|
||||
|
@ -77,7 +77,7 @@ class Phantom
|
||||
$phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$url}%22,renderType:%22pdf%22%7D";
|
||||
$pdf = CurlUtils::get($phantom_url);
|
||||
|
||||
Storage::makeDirectory($path, 0775);
|
||||
// Storage::makeDirectory($path, 0775);
|
||||
|
||||
$instance = Storage::disk(config('filesystems.default'))->put($file_path, $pdf);
|
||||
|
||||
|
@ -221,10 +221,9 @@ class SystemHealth
|
||||
}
|
||||
|
||||
try {
|
||||
Mail::to(config('mail.from.address'))
|
||||
->send(new TestMailServer('Email Server Works!', config('mail.from.address')));
|
||||
Mail::to(config('mail.from.address'))->send(new TestMailServer('Email Server Works!', config('mail.from.address')));
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
return [$e->getMessage()];
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -25,7 +25,7 @@
|
||||
],
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": ">=7.3",
|
||||
"php": "^7.3|^7.4",
|
||||
"ext-json": "*",
|
||||
"asgrim/ofxparser": "^1.2",
|
||||
"authorizenet/authorizenet": "^2.0",
|
||||
@ -66,7 +66,6 @@
|
||||
"require-dev": {
|
||||
"anahkiasen/former": "^4.2",
|
||||
"barryvdh/laravel-debugbar": "^3.4",
|
||||
"brianium/paratest": "^5.0",
|
||||
"darkaonline/l5-swagger": "^8.0",
|
||||
"facade/ignition": "^2.3.6",
|
||||
"filp/whoops": "^2.7",
|
||||
|
626
composer.lock
generated
626
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -60,7 +60,8 @@ return [
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => 'storage/',
|
||||
'root' => public_path('storage'),
|
||||
//'root' => 'storage/',
|
||||
// 'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
|
@ -12,7 +12,7 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/').'/',
|
||||
'app_domain' => env('APP_DOMAIN', ''),
|
||||
'app_version' => '5.0.31',
|
||||
'app_version' => '5.0.32',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
@ -23,7 +23,7 @@ return [
|
||||
'date_time_format' => 'Y-m-d H:i',
|
||||
'daily_email_limit' => 300,
|
||||
'error_email' => env('ERROR_EMAIL', ''),
|
||||
'mailer' => env('MAIL_MAILER',''),
|
||||
'mailer' => env('MAIL_MAILER', ''),
|
||||
'company_id' => 0,
|
||||
'hash_salt' => env('HASH_SALT', ''),
|
||||
'currency_converter_api_key' => env('OPENEXCHANGE_APP_ID', ''),
|
||||
|
@ -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"],"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/google-icon.png":["assets/images/google-icon.png"],"assets/images/logo.png":["assets/images/logo.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/payment_types/jcb.png":["assets/images/payment_types/jcb.png"],"assets/images/payment_types/laser.png":["assets/images/payment_types/laser.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"],"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"],"packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf":["packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf"]}
|
File diff suppressed because it is too large
Load Diff
6
public/flutter_service_worker.js
vendored
6
public/flutter_service_worker.js
vendored
@ -5,10 +5,10 @@ const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"version.json": "32afcc2282ccf7746c00708ef0601017",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"main.dart.js": "93b0d3c3a17ae9148c1dfd198f6b450e",
|
||||
"main.dart.js": "b3c0b8b130688ccb988a28e1ec1f4a26",
|
||||
"/": "23224b5e03519aaa87594403d54412cf",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"assets/AssetManifest.json": "ea09ed4b9b8b6c83d6896248aac7c527",
|
||||
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
|
||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "c1242726c7eac4eb5e843d826f78fb1b",
|
||||
@ -29,7 +29,7 @@ const RESOURCES = {
|
||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
||||
"assets/NOTICES": "02aa30cfaaab572341a87cc774e7ef24",
|
||||
"assets/NOTICES": "7d8f2aa600c3ae24c9efdad38c9f53b4",
|
||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35"
|
||||
|
2
public/js/clients/payments/checkout.com.js
vendored
2
public/js/clients/payments/checkout.com.js
vendored
@ -1,2 +1,2 @@
|
||||
/*! For license information please see checkout.com.js.LICENSE.txt */
|
||||
!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=8)}({8:function(e,t,n){e.exports=n("XYrq")},XYrq:function(e,t){document.getElementById("toggle-payment-with-token").addEventListener("click",(function(){document.getElementById("save-card--container").style.display="none",document.getElementById("checkout--container").style.display="none",document.getElementById("pay-now-with-token--container").style.display="block",document.querySelector("input[name=pay_with_token]").value=!0})),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",(function(){document.getElementById("save-card--container").style.display="grid",document.getElementById("checkout--container").style.display="block",document.getElementById("pay-now-with-token--container").style.display="none",document.querySelector("input[name=pay_with_token]").value=!1}));var n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;e&&(document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e.data)),document.querySelector('input[name="store_card"]').value=document.querySelector("input[name=token-billing-checkbox]:checked").value,document.getElementById("server-response").submit()};document.getElementById("pay-now-with-token").addEventListener("click",n),window.CKOConfig={publicKey:document.querySelector('meta[name="public-key"]').content,customerEmail:document.querySelector('meta[name="customer-email"]').content,value:document.querySelector('meta[name="value"]').content,currency:document.querySelector('meta[name="currency"]').content,paymentMode:"cards",cardFormMode:"cardTokenisation",cardTokenised:function(e){n(e)}}}});
|
||||
!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=8)}({8:function(e,t,n){e.exports=n("XYrq")},XYrq:function(e,t){var n,o=document.getElementById("toggle-payment-with-token"),r=document.getElementById("toggle-payment-with-credit-card"),u=document.getElementById("pay-button"),a=document.getElementById("payment-form"),c=null!==(n=document.querySelector('meta[name="public-key"]').content)&&void 0!==n?n:"";o&&o.addEventListener("click",(function(){document.getElementById("save-card--container").style.display="none",document.getElementById("checkout--container").style.display="none",document.getElementById("pay-now-with-token--container").style.display="block",document.querySelector("input[name=pay_with_token]").value=!0})),r&&r.addEventListener("click",(function(){document.getElementById("save-card--container").style.display="grid",document.getElementById("checkout--container").style.display="block",document.getElementById("pay-now-with-token--container").style.display="none",document.querySelector("input[name=pay_with_token]").value=!1}));var d=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;e&&(document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e)),document.querySelector('input[name="store_card"]').value=document.querySelector("input[name=token-billing-checkbox]:checked").value,document.getElementById("server-response").submit()};document.getElementById("pay-now-with-token")&&document.getElementById("pay-now-with-token").addEventListener("click",d),Frames.init(c),Frames.addEventHandler(Frames.Events.CARD_VALIDATION_CHANGED,(function(e){u.disabled=!Frames.isCardValid()})),Frames.addEventHandler(Frames.Events.CARD_TOKENIZED,(function(e){d(e)})),a.addEventListener("submit",(function(e){e.preventDefault(),Frames.submitCard()}))}});
|
2
public/js/setup/setup.js
vendored
2
public/js/setup/setup.js
vendored
File diff suppressed because one or more lines are too long
234616
public/main.dart.js
vendored
234616
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -6,7 +6,7 @@
|
||||
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=cddcd46c630c71737bda",
|
||||
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=caff3774673a6c683e74",
|
||||
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=5469146cd629ea1b5c20",
|
||||
"/js/clients/payments/checkout.com.js": "/js/clients/payments/checkout.com.js?id=4d5d992da65a0c422291",
|
||||
"/js/clients/payments/checkout.com.js": "/js/clients/payments/checkout.com.js?id=ce184db42e52d403c21b",
|
||||
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=c4012ad90f17d60432ad",
|
||||
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=6dbe9316b98deea55421",
|
||||
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=f4659d26a26d2f408397",
|
||||
@ -15,6 +15,6 @@
|
||||
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=85bcae0a646882e56b12",
|
||||
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=5c35d28cf0a3286e7c45",
|
||||
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=fa54bb4229aba6b0817c",
|
||||
"/js/setup/setup.js": "/js/setup/setup.js?id=f42b2dee6575623822c2",
|
||||
"/js/setup/setup.js": "/js/setup/setup.js?id=34b53878aeaca14a2b33",
|
||||
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad"
|
||||
}
|
||||
|
70
resources/js/clients/payments/checkout.com.js
vendored
70
resources/js/clients/payments/checkout.com.js
vendored
@ -8,9 +8,17 @@
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
document
|
||||
.getElementById('toggle-payment-with-token')
|
||||
.addEventListener('click', () => {
|
||||
const paymentWithToken = document.getElementById('toggle-payment-with-token');
|
||||
const paymentWithCreditCard = document.getElementById(
|
||||
'toggle-payment-with-credit-card'
|
||||
);
|
||||
const payButton = document.getElementById('pay-button');
|
||||
const form = document.getElementById('payment-form');
|
||||
const publicKey =
|
||||
document.querySelector('meta[name="public-key"]').content ?? '';
|
||||
|
||||
if (paymentWithToken) {
|
||||
paymentWithToken.addEventListener('click', () => {
|
||||
document.getElementById('save-card--container').style.display = 'none';
|
||||
document.getElementById('checkout--container').style.display = 'none';
|
||||
document.getElementById('pay-now-with-token--container').style.display =
|
||||
@ -18,10 +26,10 @@ document
|
||||
|
||||
document.querySelector('input[name=pay_with_token]').value = true;
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById('toggle-payment-with-credit-card')
|
||||
.addEventListener('click', () => {
|
||||
if (paymentWithCreditCard) {
|
||||
paymentWithCreditCard.addEventListener('click', () => {
|
||||
document.getElementById('save-card--container').style.display = 'grid';
|
||||
document.getElementById('checkout--container').style.display = 'block';
|
||||
document.getElementById('pay-now-with-token--container').style.display =
|
||||
@ -29,12 +37,13 @@ document
|
||||
|
||||
document.querySelector('input[name=pay_with_token]').value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const completePayment = (data = null) => {
|
||||
if (data) {
|
||||
document.querySelector(
|
||||
'input[name="gateway_response"]'
|
||||
).value = JSON.stringify(data.data);
|
||||
).value = JSON.stringify(data);
|
||||
}
|
||||
|
||||
document.querySelector(
|
||||
@ -46,19 +55,36 @@ const completePayment = (data = null) => {
|
||||
document.getElementById('server-response').submit();
|
||||
};
|
||||
|
||||
document
|
||||
.getElementById('pay-now-with-token')
|
||||
.addEventListener('click', completePayment);
|
||||
if (document.getElementById('pay-now-with-token')) {
|
||||
document
|
||||
.getElementById('pay-now-with-token')
|
||||
.addEventListener('click', completePayment);
|
||||
}
|
||||
|
||||
window.CKOConfig = {
|
||||
publicKey: document.querySelector('meta[name="public-key"]').content,
|
||||
customerEmail: document.querySelector('meta[name="customer-email"]')
|
||||
.content,
|
||||
value: document.querySelector('meta[name="value"]').content,
|
||||
currency: document.querySelector('meta[name="currency"]').content,
|
||||
paymentMode: 'cards',
|
||||
cardFormMode: 'cardTokenisation',
|
||||
cardTokenised: function(event) {
|
||||
completePayment(event);
|
||||
},
|
||||
};
|
||||
// window.CKOConfig = {
|
||||
// publicKey: document.querySelector('meta[name="public-key"]').content,
|
||||
// customerEmail: document.querySelector('meta[name="customer-email"]')
|
||||
// .content,
|
||||
// value: document.querySelector('meta[name="value"]').content,
|
||||
// currency: document.querySelector('meta[name="currency"]').content,
|
||||
// paymentMode: 'cards',
|
||||
// cardFormMode: 'cardTokenisation',
|
||||
// cardTokenised: function(event) {
|
||||
// completePayment(event);
|
||||
// },
|
||||
// };
|
||||
|
||||
Frames.init(publicKey);
|
||||
|
||||
Frames.addEventHandler(Frames.Events.CARD_VALIDATION_CHANGED, function(event) {
|
||||
payButton.disabled = !Frames.isCardValid();
|
||||
});
|
||||
|
||||
Frames.addEventHandler(Frames.Events.CARD_TOKENIZED, function(event) {
|
||||
completePayment(event)
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
Frames.submitCard();
|
||||
});
|
||||
|
17
resources/js/setup/setup.js
vendored
17
resources/js/setup/setup.js
vendored
@ -32,7 +32,9 @@ class Setup {
|
||||
|
||||
Axios.post('/setup/check_db', data)
|
||||
.then((response) => this.handleSuccess(this.checkDbAlert))
|
||||
.catch((e) => this.handleFailure(this.checkDbAlert, e.response.data.message));
|
||||
.catch((e) =>
|
||||
this.handleFailure(this.checkDbAlert, e.response.data.message)
|
||||
);
|
||||
}
|
||||
|
||||
handleSmtpCheck() {
|
||||
@ -51,9 +53,14 @@ class Setup {
|
||||
.value,
|
||||
};
|
||||
|
||||
this.checkSmtpButton.disabled = true;
|
||||
|
||||
Axios.post('/setup/check_mail', data)
|
||||
.then((response) => this.handleSuccess(this.checkSmtpAlert))
|
||||
.catch((e) => this.handleFailure(this.checkSmtpAlert));
|
||||
.catch((e) =>
|
||||
this.handleFailure(this.checkSmtpAlert, e.response.data.message)
|
||||
)
|
||||
.finally(() => (this.checkSmtpButton.disabled = false));
|
||||
}
|
||||
|
||||
handleTestPdfCheck() {
|
||||
@ -71,7 +78,7 @@ class Setup {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
this.handleFailure(this.checkPdfAlert)
|
||||
this.handleFailure(this.checkPdfAlert);
|
||||
});
|
||||
}
|
||||
|
||||
@ -83,7 +90,9 @@ class Setup {
|
||||
|
||||
handleFailure(element, message = null) {
|
||||
element.classList.remove('alert-success');
|
||||
element.innerText = message ? message : "Oops, looks like something isn't correct!";
|
||||
element.innerText = message
|
||||
? message
|
||||
: "Oops, looks like something isn't correct!";
|
||||
element.classList.add('alert-failure');
|
||||
}
|
||||
|
||||
|
@ -3313,4 +3313,5 @@ return [
|
||||
'shipping_country' => 'Shipping Country',
|
||||
|
||||
'service' => 'Service',
|
||||
'pay' => 'Pay',
|
||||
];
|
||||
|
@ -55,7 +55,7 @@
|
||||
{!! \App\Utils\Number::formatMoney($payment->amount, $payment->client) !!}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
{{ $payment->transaction_reference }}
|
||||
{{ \Illuminate\Support\Str::limit($payment->transaction_reference, 35) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
{!! \App\Models\Payment::badgeForStatus($payment->status_id) !!}
|
||||
|
@ -6,6 +6,9 @@
|
||||
<meta name="value" content="{{ $value }}">
|
||||
<meta name="currency" content="{{ $currency }}">
|
||||
<meta name="reference" content="{{ $payment_hash }}">
|
||||
|
||||
<style>*,*::after,*::before{box-sizing:border-box}html{background-color:#FFF;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif}#payment-form{width:31.5rem;margin:0 auto}iframe{width:100%}.one-liner{display:flex;flex-direction:column}#pay-button{border:none;border-radius:3px;color:#FFF;font-weight:500;height:40px;width:100%;background-color:#13395E;box-shadow:0 1px 3px 0 rgba(19,57,94,0.4)}#pay-button:active{background-color:#0B2A49;box-shadow:0 1px 3px 0 rgba(19,57,94,0.4)}#pay-button:hover{background-color:#15406B;box-shadow:0 2px 5px 0 rgba(19,57,94,0.4)}#pay-button:disabled{background-color:#697887;box-shadow:none}#pay-button:not(:disabled){cursor:pointer}.card-frame{border:solid 1px #13395E;border-radius:3px;width:100%;margin-bottom:8px;height:40px;box-shadow:0 1px 3px 0 rgba(19,57,94,0.2)}.card-frame.frame--rendered{opacity:1}.card-frame.frame--rendered.frame--focus{border:solid 1px #13395E;box-shadow:0 2px 5px 0 rgba(19,57,94,0.15)}.card-frame.frame--rendered.frame--invalid{border:solid 1px #D96830;box-shadow:0 2px 5px 0 rgba(217,104,48,0.15)}.success-payment-message{color:#13395E;line-height:1.4}.token{color:#b35e14;font-size:0.9rem;font-family:monospace}@media screen and (min-width: 31rem){.one-liner{flex-direction:row}.card-frame{width:318px;margin-bottom:0}#pay-button{width:175px;margin-left:8px}}</style>
|
||||
<script src="https://cdn.checkout.com/js/framesv2.min.js"></script>
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
@ -34,13 +37,16 @@
|
||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||
<label class="mr-4">
|
||||
<input
|
||||
type="radio"
|
||||
id="toggle-payment-with-token"
|
||||
class="form-radio cursor-pointer" name="payment-type" />
|
||||
<span class="ml-1 cursor-pointer">**** {{ $token->meta->last4 }}</span>
|
||||
</label>
|
||||
@isset($token)
|
||||
<label class="mr-4">
|
||||
<input
|
||||
type="radio"
|
||||
id="toggle-payment-with-token"
|
||||
class="form-radio cursor-pointer" name="payment-type" />
|
||||
<span class="ml-1 cursor-pointer">**** {{ $token->meta->last4 }}</span>
|
||||
</label>
|
||||
@endisset
|
||||
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
@ -55,13 +61,20 @@
|
||||
@include('portal.ninja2020.gateways.includes.save_card')
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<form class="payment-form" method="POST" action="#" id="checkout--container">
|
||||
@if(app()->environment() == 'production')
|
||||
<script async src="https://cdn.checkout.com/js/checkout.js"></script>
|
||||
@else
|
||||
<script async src="https://cdn.checkout.com/sandbox/js/checkout.js"></script>
|
||||
@endif
|
||||
</form>
|
||||
<div id="checkout--container">
|
||||
<form id="payment-form" method="POST" action="#">
|
||||
<div class="one-liner">
|
||||
<div class="card-frame">
|
||||
<!-- form will be added here -->
|
||||
</div>
|
||||
<!-- add submit button -->
|
||||
<button id="pay-button" disabled>
|
||||
{{ ctrans('texts.pay') }} {{ App\Utils\Number::formatMoney($total['amount_with_fee'], $client) }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="success-payment-message"></p>
|
||||
</form>
|
||||
</div>
|
||||
@endcomponent
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
|
@ -180,6 +180,6 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
Route::post('support/messages/send', 'Support\Messages\SendingController');
|
||||
});
|
||||
|
||||
Route::match(['get', 'post'], 'payment_webhook/{company_key}/{gateway_key}', 'PaymentWebhookController');
|
||||
Route::match(['get', 'post'], 'payment_webhook/{company_key?}/{gateway_key?}', 'PaymentWebhookController');
|
||||
|
||||
Route::fallback('BaseController@notFound');
|
||||
|
@ -24,7 +24,7 @@ Route::get('tmp_pdf/{hash}', 'ClientPortal\TempRouteController@index')->name('tm
|
||||
Route::get('client/key_login/{contact_key}', 'ClientPortal\ContactHashLoginController@login')->name('client.contact_login')->middleware(['contact_key_login']);
|
||||
|
||||
//todo implement domain DB
|
||||
Route::group(['middleware' => ['auth:contact', 'locale'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||
Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||
Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
|
||||
|
||||
Route::get('invoices', 'ClientPortal\InvoiceController@index')->name('invoices.index')->middleware('portal_enabled');
|
||||
|
328
tests/Feature/DeleteInvoiceTest.php
Normal file
328
tests/Feature/DeleteInvoiceTest.php
Normal file
@ -0,0 +1,328 @@
|
||||
<?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 Tests\Feature;
|
||||
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class DeleteInvoiceTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
use MakesHash;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers App\Services\Invoice\MarkInvoiceDeleted
|
||||
*/
|
||||
public function testInvoiceDeletion()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => 'A Nice Client',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$client_hash_id = $arr['data']['id'];
|
||||
$client = Client::find($this->decodePrimaryKey($client_hash_id));
|
||||
|
||||
$this->assertEquals($client->balance, 0);
|
||||
$this->assertEquals($client->paid_to_date, 0);
|
||||
//create new invoice.
|
||||
|
||||
$line_items = [];
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 1;
|
||||
$item->cost = 10;
|
||||
|
||||
$line_items[] = (array)$item;
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 1;
|
||||
$item->cost = 10;
|
||||
|
||||
$line_items[] = (array)$item;
|
||||
|
||||
$invoice = [
|
||||
'status_id' => 1,
|
||||
'number' => '',
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'po_number' => '3434343',
|
||||
'public_notes' => 'notes',
|
||||
'is_deleted' => 0,
|
||||
'custom_value1' => 0,
|
||||
'custom_value2' => 0,
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'client_id' => $client_hash_id,
|
||||
'line_items' => (array)$line_items,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/', $invoice)
|
||||
->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$invoice_one_hashed_id = $arr['data']['id'];
|
||||
|
||||
$invoice = Invoice::find($this->decodePrimaryKey($invoice_one_hashed_id));
|
||||
|
||||
$invoice = $invoice->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(20, $invoice->balance);
|
||||
$this->assertEquals(20, $invoice->client->balance);
|
||||
|
||||
//delete invoice
|
||||
$data = [
|
||||
'ids' => [$invoice_one_hashed_id],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/bulk?action=delete', $data)->assertStatus(200);
|
||||
|
||||
|
||||
$invoice = $invoice->fresh();
|
||||
|
||||
$this->assertEquals(20, $invoice->balance);
|
||||
$this->assertEquals(0, $invoice->client->balance);
|
||||
$this->assertTrue((bool)$invoice->is_deleted);
|
||||
$this->assertNotNull($invoice->deleted_at);
|
||||
|
||||
//delete invoice
|
||||
$data = [
|
||||
'ids' => [$invoice_one_hashed_id],
|
||||
];
|
||||
|
||||
//restore invoice
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/bulk?action=restore', $data)->assertStatus(200);
|
||||
|
||||
$invoice = $invoice->fresh();
|
||||
|
||||
$this->assertEquals(20, $invoice->balance);
|
||||
$this->assertFalse((bool)$invoice->is_deleted);
|
||||
$this->assertNull($invoice->deleted_at);
|
||||
$this->assertEquals(20, $invoice->client->fresh()->balance);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers App\Services\Invoice\HandleRestore
|
||||
*/
|
||||
public function testInvoiceDeletionAndRestoration()
|
||||
{
|
||||
//create new client
|
||||
|
||||
$data = [
|
||||
'name' => 'A Nice Client',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$client_hash_id = $arr['data']['id'];
|
||||
$client = Client::find($this->decodePrimaryKey($client_hash_id));
|
||||
|
||||
$this->assertEquals($client->balance, 0);
|
||||
$this->assertEquals($client->paid_to_date, 0);
|
||||
//create new invoice.
|
||||
|
||||
$line_items = [];
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 1;
|
||||
$item->cost = 10;
|
||||
|
||||
$line_items[] = (array)$item;
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 1;
|
||||
$item->cost = 10;
|
||||
|
||||
$line_items[] = (array)$item;
|
||||
|
||||
$invoice = [
|
||||
'status_id' => 1,
|
||||
'number' => '',
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'po_number' => '3434343',
|
||||
'public_notes' => 'notes',
|
||||
'is_deleted' => 0,
|
||||
'custom_value1' => 0,
|
||||
'custom_value2' => 0,
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'client_id' => $client_hash_id,
|
||||
'line_items' => (array)$line_items,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/', $invoice)
|
||||
->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$invoice_one_hashed_id = $arr['data']['id'];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/', $invoice)
|
||||
->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$invoice_two_hashed_id = $arr['data']['id'];
|
||||
|
||||
//mark as paid
|
||||
|
||||
$data = [
|
||||
'amount' => 40.0,
|
||||
'client_id' => $client_hash_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $invoice_one_hashed_id,
|
||||
'amount' => 20.0,
|
||||
],
|
||||
[
|
||||
'invoice_id' => $invoice_two_hashed_id,
|
||||
'amount' => 20.0,
|
||||
],
|
||||
],
|
||||
'date' => '2020/12/01',
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/payments?include=invoices', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
}
|
||||
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
$payment_hashed_id = $arr['data']['id'];
|
||||
|
||||
$invoice_one = Invoice::find($this->decodePrimaryKey($invoice_one_hashed_id));
|
||||
$invoice_two = Invoice::find($this->decodePrimaryKey($invoice_two_hashed_id));
|
||||
$payment = Payment::find($this->decodePrimaryKey($payment_hashed_id));
|
||||
|
||||
$this->assertEquals(20, $invoice_one->company_ledger->sortByDesc('id')->first()->balance);
|
||||
|
||||
//test balance
|
||||
$this->assertEquals($invoice_one->amount, 20);
|
||||
$this->assertEquals($invoice_one->balance, 0);
|
||||
$this->assertEquals($invoice_two->amount, 20);
|
||||
$this->assertEquals($invoice_two->balance, 0);
|
||||
|
||||
$this->assertEquals($client->fresh()->paid_to_date, 40);
|
||||
$this->assertEquals($client->balance, 0);
|
||||
|
||||
//hydrate associated payment
|
||||
$this->assertEquals($payment->amount, 40);
|
||||
$this->assertEquals($payment->applied, 40);
|
||||
|
||||
//delete invoice
|
||||
$data = [
|
||||
'ids' => [$invoice_one_hashed_id],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/bulk?action=delete', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertTrue($arr['data'][0]['is_deleted']);
|
||||
|
||||
$this->assertEquals(20, $client->fresh()->paid_to_date);
|
||||
$this->assertEquals(0, $client->fresh()->balance);
|
||||
$this->assertEquals(20, $payment->fresh()->applied);
|
||||
$this->assertEquals(20, $payment->fresh()->amount);
|
||||
|
||||
$invoice_one = $invoice_one->fresh();
|
||||
|
||||
$this->assertTrue((bool)$invoice_one->is_deleted);
|
||||
$this->assertNotNull($invoice_one->deleted_at);
|
||||
|
||||
//restore invoice
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/invoices/bulk?action=restore', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertFalse($arr['data'][0]['is_deleted']);
|
||||
|
||||
$invoice_one = $invoice_one->fresh();
|
||||
$this->assertFalse((bool)$invoice_one->is_deleted);
|
||||
$this->assertNull($invoice_one->deleted_at);
|
||||
|
||||
$payment = $payment->fresh();
|
||||
|
||||
$this->assertEquals(40, $payment->fresh()->applied);
|
||||
$this->assertEquals(40, $payment->fresh()->amount);
|
||||
$this->assertEquals(40, $client->fresh()->paid_to_date);
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class MockAccountData.
|
||||
@ -157,6 +158,9 @@ trait MockAccountData
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
|
||||
Storage::makeDirectory($this->company->company_key.'/documents', 0755, true);
|
||||
Storage::makeDirectory($this->company->company_key.'/images', 0755, true);
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
|
||||
$settings->company_logo = 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png';
|
||||
@ -219,6 +223,10 @@ trait MockAccountData
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
|
||||
Storage::makeDirectory($this->company->company_key.'/'.$this->client->client_hash.'/invoices', 0755, true);
|
||||
Storage::makeDirectory($this->company->company_key.'/'.$this->client->client_hash.'/credits', 0755, true);
|
||||
Storage::makeDirectory($this->company->company_key.'/'.$this->client->client_hash.'/quotes', 0755, true);
|
||||
|
||||
$contact = ClientContact::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
|
Loading…
Reference in New Issue
Block a user