mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-09-20 08:21:34 +02:00
Merge branch 'v5-develop' into v5-develop
Signed-off-by: David Bomba <turbo124@gmail.com>
This commit is contained in:
commit
5a1f52941d
9
.github/workflows/phpunit.yml
vendored
9
.github/workflows/phpunit.yml
vendored
@ -13,8 +13,8 @@ jobs:
|
||||
runs-on: ${{ matrix.operating-system }}
|
||||
strategy:
|
||||
matrix:
|
||||
operating-system: ['ubuntu-20.04', 'ubuntu-22.04']
|
||||
php-versions: ['8.1','8.2']
|
||||
operating-system: ['ubuntu-22.04','ubuntu-24.04']
|
||||
php-versions: ['8.2']
|
||||
phpunit-versions: ['latest']
|
||||
ci_node_total: [ 8 ]
|
||||
ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7]
|
||||
@ -95,14 +95,15 @@ jobs:
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
- uses: actions/cache@v3
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.php }}-composer-
|
||||
|
||||
|
||||
- name: Install composer dependencies
|
||||
run: |
|
||||
composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@ -1 +1 @@
|
||||
5.8.57
|
||||
5.8.57
|
@ -1,69 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\DataProviders\FatturaPADataProvider;
|
||||
|
||||
class EDocLint extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:edoclint';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Builds json files component data maps';
|
||||
|
||||
private array $classes = [
|
||||
FatturaPADataProvider::class,
|
||||
];
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
foreach($this->classes as $class)
|
||||
{
|
||||
|
||||
$provider = new $class();
|
||||
|
||||
foreach($provider as $key => $value) {
|
||||
|
||||
$json = json_encode($provider->{$key}, JSON_PRETTY_PRINT);
|
||||
Storage::disk('local')->put($key.'.json', $json);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
24
app/DataProviders/FACT1.php
Normal file
24
app/DataProviders/FACT1.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\DataProviders;
|
||||
|
||||
/**
|
||||
* Class FACT1.
|
||||
*/
|
||||
class FACT1
|
||||
{
|
||||
public function build()
|
||||
{
|
||||
$i = new \InvoiceNinja\EInvoice\Models\FACT1\Invoice();
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\DataProviders;
|
||||
|
||||
class FatturaPADataProvider
|
||||
{
|
||||
public array $regime_fiscale = [
|
||||
"RF01" => "Regime ordinario",
|
||||
"RF02" => "Regime dei contribuenti minimi (art. 1,c.96-117, L. 244/2007)",
|
||||
"RF04" => "Agricoltura e attività connesse e pesca (artt. 34 e 34-bis, D.P.R. 633/1972)",
|
||||
"RF05" => "Vendita sali e tabacchi (art. 74, c.1, D.P.R. 633/1972)",
|
||||
"RF06" => "Commercio dei fiammiferi (art. 74, c.1, D.P.R. 633/1972)",
|
||||
"RF07" => "Editoria (art. 74, c.1, D.P.R. 633/1972)",
|
||||
"RF08" => "Gestione di servizi di telefonia pubblica (art. 74, c.1, D.P.R. 633/1972)" ,
|
||||
"RF09" => "Rivendita di documenti di trasporto pubblico e di sosta (art. 74, c.1, D.P.R. 633/1972)" ,
|
||||
"RF10" => "Intrattenimenti, giochi e altre attività di cui alla tariffa allegata al D.P.R. 640/72 (art. 74, c.6, D.P.R. 633/1972)" ,
|
||||
"RF11" => "Agenzie di viaggi e turismo (art. 74-ter, D.P.R. 633/1972)" ,
|
||||
"RF12" => "Agriturismo (art. 5, c.2, L. 413/1991)" ,
|
||||
"RF13" => "Vendite a domicilio (art. 25-bis, c.6, D.P.R. 600/1973)" ,
|
||||
"RF14" => "Rivendita di beni usati, di oggetti d’arte, d’antiquariato o da collezione (art. 36, D.L. 41/1995)" ,
|
||||
"RF15" => "Agenzie di vendite all’asta di oggetti d’arte, antiquariato o da collezione (art. 40-bis, D.L. 41/1995)" ,
|
||||
"RF16" => "IVA per cassa P.A. (art. 6, c.5, D.P.R. 633/1972)" ,
|
||||
"RF17" => "IVA per cassa (art. 32-bis, D.L. 83/2012)" ,
|
||||
"RF19" => "Regime forfettario" ,
|
||||
"RF18" => "Altro"
|
||||
];
|
||||
|
||||
public array $tipo_documento = [
|
||||
'TD01' => 'Fattura',
|
||||
'TD02' => 'Acconto/Anticipo su fattura',
|
||||
'TD03' => 'Acconto/Anticipo su parcella',
|
||||
'TD04' => 'Nota di Credito',
|
||||
'TD05' => 'Nota di Debito',
|
||||
'TD06' => 'Parcella',
|
||||
'TD16' => 'Integrazione fattura reverse charge interno',
|
||||
'TD17' => 'Integrazione/autofattura per acquisto servizi dall’estero',
|
||||
'TD18' => 'Integrazione per acquisto di beni intracomunitari',
|
||||
'TD19' => 'Integrazione/autofattura per acquisto di beni ex art.17 c.2 DPR 633/72',
|
||||
'TD20' => 'Autofattura per regolarizzazione e integrazione delle fatture',
|
||||
'TD21' => 'Autofattura per splafonamento',
|
||||
'TD22' => 'Estrazione beni da Deposito IVA',
|
||||
'TD23' => 'Estrazione beni da Deposito IVA con versamento dell’IVA',
|
||||
'TD24' => 'Fattura differita di cui all’art.21, comma 4, lett. a)',
|
||||
'TD25' => 'Fattura differita di cui all’art.21, comma 4, terzo periodo lett. b)',
|
||||
'TD26' => 'Cessione di beni ammortizzabili e per passaggi interni ',
|
||||
'TD27' => 'Fattura per autoconsumo o per cessioni gratuite senza rivalsa',
|
||||
];
|
||||
|
||||
public array $esigibilita_iva = [
|
||||
'I' => 'IVA ad esigibilità immediata',
|
||||
'D' => 'IVA ad esigibilità differita',
|
||||
'S' => 'Scissione dei pagamenti',
|
||||
];
|
||||
|
||||
public array $modalita_pagamento = [
|
||||
'MP01' => 'contanti', //cash
|
||||
'MP02' => 'assegno', //check
|
||||
'MP03' => 'assegno circolare', //cashier's check
|
||||
'MP04' => 'contanti presso Tesoreria', //cash at treasury
|
||||
'MP05' => 'bonifico', //bank transfer
|
||||
'MP06' => 'vaglia cambiario', //bill of exchange
|
||||
'MP07' => 'bollettino bancario', //bank bulletin
|
||||
'MP08' => 'carta di pagamento', //payment card
|
||||
'MP09' => 'RID', //RID
|
||||
'MP10' => 'RID utenze', //RID utilities
|
||||
'MP11' => 'RID veloce', //fast RID
|
||||
'MP12' => 'Riba', //Riba
|
||||
'MP13' => 'MAV //MAV',
|
||||
'MP14' => 'quietanza erario stato', //state treasury receipt
|
||||
'MP15' => 'giroconto su conti di contabilità speciale', //transfer to special accounting accounts
|
||||
'MP16' => 'domiciliazione bancaria', //bank domiciliation
|
||||
'MP17' => 'domiciliazione postale', //postal domiciliation
|
||||
'MP18' => 'bollettino di c/c postale', //postal giro account
|
||||
'MP19' => 'SEPA Direct Debit', //SEPA Direct Debit
|
||||
'MP20' => 'SEPA Direct Debit CORE', //SEPA Direct Debit CORE
|
||||
'MP21' => 'SEPA Direct Debit B2B', //SEPA Direct Debit B2B
|
||||
'MP22' => 'Trattenuta su somme già riscosse', //Withholding on sums already collected
|
||||
'MP23' => 'PagoPA', //PagoPA
|
||||
];
|
||||
|
||||
public array $esigibilita_pagamento = [
|
||||
'TP01' => 'Pagamento a rate',
|
||||
'TP02' => 'Pagamento completo',
|
||||
'TP03' => 'Anticipo',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function getRegimeFiscale(): array
|
||||
{
|
||||
return $this->regime_fiscale;
|
||||
}
|
||||
public function getTipoDocumento(): array
|
||||
{
|
||||
return $this->tipo_documento;
|
||||
}
|
||||
|
||||
public function getEsigibilitaIva(): array
|
||||
{
|
||||
return $this->esigibilita_iva;
|
||||
}
|
||||
|
||||
public function getModalitaPagamento(): array
|
||||
{
|
||||
return $this->modalita_pagamento;
|
||||
}
|
||||
|
||||
public function getEsigibilitaPagamento(): array
|
||||
{
|
||||
return $this->esigibilita_pagamento;
|
||||
}
|
||||
|
||||
}
|
@ -125,8 +125,10 @@ class ClientExport extends BaseExport
|
||||
|
||||
$query = Client::query()->with('contacts')
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false)
|
||||
$query->where('is_deleted', 0);
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -82,8 +82,12 @@ class ExpenseExport extends BaseExport
|
||||
$query = Expense::query()
|
||||
->with('client')
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -60,8 +60,12 @@ class InvoiceExport extends BaseExport
|
||||
->whereHas('client', function ($q){
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -73,8 +73,11 @@ class InvoiceItemExport extends BaseExport
|
||||
->whereHas('client', function ($q){
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -73,8 +73,12 @@ class ProductExport extends BaseExport
|
||||
|
||||
$query = Product::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', 0);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -61,8 +61,11 @@ class PurchaseOrderExport extends BaseExport
|
||||
->whereHas('vendor', function ($q){
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -65,8 +65,11 @@ class PurchaseOrderItemExport extends BaseExport
|
||||
->whereHas('vendor', function ($q){
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->with('vendor')->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->with('vendor')->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -67,8 +67,11 @@ class QuoteExport extends BaseExport
|
||||
->whereHas('client', function ($q){
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -68,8 +68,11 @@ class QuoteItemExport extends BaseExport
|
||||
->whereHas('client', function ($q){
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->with('client')->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->with('client')->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -59,8 +59,11 @@ class RecurringInvoiceExport extends BaseExport
|
||||
->whereHas('client', function ($q){
|
||||
$q->where('is_deleted', false);
|
||||
})
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -68,8 +68,11 @@ class TaskExport extends BaseExport
|
||||
|
||||
$query = Task::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -62,8 +62,11 @@ class VendorExport extends BaseExport
|
||||
|
||||
$query = Vendor::query()->with('contacts')
|
||||
->withTrashed()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', $this->input['include_deleted'] ?? false);
|
||||
->where('company_id', $this->company->id);
|
||||
|
||||
if(!$this->input['include_deleted'] ?? false){
|
||||
$query->where('is_deleted', 0);
|
||||
}
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UserFactory
|
||||
{
|
||||
@ -29,6 +30,7 @@ class UserFactory
|
||||
$user->signature = '';
|
||||
$user->theme_id = 0;
|
||||
$user->user_logged_in_notification = true;
|
||||
$user->referral_code = Str::lower(Str::random(32));
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
@ -64,23 +64,23 @@ class GmailTransport extends AbstractTransport
|
||||
|
||||
$body->setRaw($this->base64_encode($bcc_list.$message->toString()));
|
||||
|
||||
try {
|
||||
// try {
|
||||
$service->users_messages->send('me', $body, []);
|
||||
} catch(\Google\Service\Exception $e) {
|
||||
/* Need to slow down */
|
||||
if ($e->getCode() == '429') {
|
||||
nlog("429 google - retrying ");
|
||||
// } catch(\Google\Service\Exception $e) {
|
||||
// /* Need to slow down */
|
||||
// if ($e->getCode() == '429') {
|
||||
// nlog("429 google - retrying ");
|
||||
|
||||
sleep(rand(3,8));
|
||||
// sleep(rand(3,8));
|
||||
|
||||
try {
|
||||
$service->users_messages->send('me', $body, []);
|
||||
} catch(\Google\Service\Exception $e) {
|
||||
// try {
|
||||
// $service->users_messages->send('me', $body, []);
|
||||
// } catch(\Google\Service\Exception $e) {
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private function base64_encode($data)
|
||||
|
@ -35,7 +35,7 @@ class NordigenController extends BaseController
|
||||
/** @var array $context */
|
||||
$context = $request->getTokenContent();
|
||||
$company = $request->getCompany();
|
||||
$lang = $company->locale();
|
||||
$lang = substr($company->locale(), 0, 2);
|
||||
$context["lang"] = $lang;
|
||||
|
||||
if (!$context) {
|
||||
@ -143,7 +143,7 @@ class NordigenController extends BaseController
|
||||
$data = $request->all();
|
||||
$company = $request->getCompany();
|
||||
$account = $company->account;
|
||||
$lang = $company->locale();
|
||||
$lang = substr($company->locale(), 0, 2);
|
||||
|
||||
/** @var array $context */
|
||||
$context = $request->getTokenContent();
|
||||
|
@ -38,6 +38,7 @@ use App\Transformers\ArraySerializer;
|
||||
use App\Transformers\EntityTransformer;
|
||||
use League\Fractal\Resource\Collection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Invoiceninja\Einvoice\Decoder\Schema;
|
||||
use League\Fractal\Serializer\JsonApiSerializer;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
@ -996,8 +997,8 @@ class BaseController extends Controller
|
||||
|
||||
if(request()->has('einvoice')){
|
||||
|
||||
// $ro = new Schema();
|
||||
// $response_data['einvoice_schema'] = $ro('FACT1');
|
||||
$ro = new Schema();
|
||||
$response_data['einvoice_schema'] = $ro('FACT1');
|
||||
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
@ -40,6 +41,7 @@ use Twig\Error\SyntaxError;
|
||||
|
||||
class PreviewController extends BaseController
|
||||
{
|
||||
use GeneratesCounter;
|
||||
use MakesHash;
|
||||
use MakesInvoiceHtml;
|
||||
use PageNumbering;
|
||||
@ -404,23 +406,24 @@ class PreviewController extends BaseController
|
||||
|
||||
/** @var \App\Models\Client $client */
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\ClientContact $contact */
|
||||
$contact = ClientContact::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
'client_id' => $client->id,
|
||||
'is_primary' => 1,
|
||||
'send_email' => true,
|
||||
]);
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$settings = $company->settings;
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$invoice = Invoice::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
'client_id' => $client->id,
|
||||
'terms' => $company->settings->invoice_terms,
|
||||
@ -428,8 +431,18 @@ class PreviewController extends BaseController
|
||||
'public_notes' => 'Sample Public Notes',
|
||||
]);
|
||||
|
||||
if ($settings->invoice_number_pattern) {
|
||||
$invoice->number = $this->getFormattedEntityNumber(
|
||||
$invoice,
|
||||
rand(1, 9999),
|
||||
$settings->counter_padding ?: 4,
|
||||
$settings->invoice_number_pattern,
|
||||
);
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
$invitation = InvoiceInvitation::factory()->create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
'invoice_id' => $invoice->id,
|
||||
'client_contact_id' => $contact->id,
|
||||
@ -454,7 +467,7 @@ class PreviewController extends BaseController
|
||||
'template' => $design->elements([
|
||||
'client' => $invoice->client,
|
||||
'entity' => $invoice,
|
||||
'pdf_variables' => (array) $invoice->company->settings->pdf_variables,
|
||||
'pdf_variables' => (array) $settings->pdf_variables,
|
||||
'products' => request()->design['design']['product'],
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
|
@ -26,8 +26,6 @@ class SelfUpdateController extends BaseController
|
||||
use ClientGroupSettingsSaver;
|
||||
use AppSetup;
|
||||
|
||||
// private bool $use_zip = false;
|
||||
|
||||
private string $filename = 'invoiceninja.tar';
|
||||
|
||||
private array $purge_file_list = [
|
||||
|
@ -13,6 +13,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Utils\Statics;
|
||||
use Illuminate\Http\Response;
|
||||
use Invoiceninja\Einvoice\Decoder\Schema;
|
||||
|
||||
class StaticController extends BaseController
|
||||
{
|
||||
@ -60,8 +61,8 @@ class StaticController extends BaseController
|
||||
|
||||
if(request()->has('einvoice')){
|
||||
|
||||
// $schema = new Schema();
|
||||
// $response_data['einvoice_schema'] = $schema('FACT1');
|
||||
$schema = new Schema();
|
||||
$response_data['einvoice_schema'] = $schema('FACT1');
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,56 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\User;
|
||||
use App\PaymentDrivers\WePayPaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class WePayController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Initialize WePay Signup.
|
||||
*/
|
||||
public function signup(string $token)
|
||||
{
|
||||
// return render('gateways.wepay.signup.finished');
|
||||
|
||||
$hash = Cache::get($token);
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($hash['company_key']);
|
||||
|
||||
$user = User::findOrFail($hash['user_id']);
|
||||
|
||||
$company = Company::where('company_key', $hash['company_key'])->firstOrFail();
|
||||
|
||||
$data['user_id'] = $user->id;
|
||||
$data['user_company'] = $company;
|
||||
|
||||
// $data['company_key'] = $company->company_key;
|
||||
// $data['db'] = $company->db;
|
||||
|
||||
$wepay_driver = new WePayPaymentDriver(new CompanyGateway(), null, null);
|
||||
|
||||
return $wepay_driver->setup($data);
|
||||
}
|
||||
|
||||
public function finished()
|
||||
{
|
||||
return render('gateways.wepay.signup.finished');
|
||||
}
|
||||
}
|
@ -35,4 +35,14 @@ class UpdateCompanyUserRequest extends Request
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(isset($input['company_user']['user']))
|
||||
unset($input['company_user']['user']);
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,9 @@ class ConnectNordigenBankIntegrationRequest extends Request
|
||||
|
||||
$context = $this->getTokenContent();
|
||||
|
||||
if(isset($context['institution_id']))
|
||||
$input['institution_id'] = $context['institution_id'];
|
||||
|
||||
$input["redirect"] = isset($context["is_react"]) && $context['is_react'] ? config('ninja.react_url') . "/#/settings/bank_accounts" : config('ninja.app_url');
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -26,7 +26,7 @@ class GenericReportRequest extends Request
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->checkAuthority();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
@ -68,27 +68,25 @@ class GenericReportRequest extends Request
|
||||
|
||||
$input['user_id'] = auth()->user()->id;
|
||||
|
||||
if(!$this->checkAuthority()){
|
||||
$input['date_range'] = '';
|
||||
$input['start_date'] = '';
|
||||
$input['end_date'] = '';
|
||||
$input['send_email'] = true;
|
||||
$input['report_keys'] = [];
|
||||
$input['document_email_attachment'] = false;
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
private function checkAuthority()
|
||||
{
|
||||
$this->error_message = ctrans('texts.authorization_failure');
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
if(Ninja::isHosted() && $user->account->isFreeHostedClient()){
|
||||
$this->error_message = ctrans('texts.upgrade_to_view_reports');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return $user->isAdmin() || $user->hasPermission('view_reports');
|
||||
|
||||
}
|
||||
|
||||
protected function failedAuthorization()
|
||||
{
|
||||
throw new AuthorizationException($this->error_message);
|
||||
}
|
||||
}
|
||||
|
@ -289,6 +289,8 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
private function createPayment($invoices, float $amount): void
|
||||
{
|
||||
|
||||
$this->attachable_invoices = [];
|
||||
$this->available_balance = $amount;
|
||||
|
||||
\DB::connection(config('database.default'))->transaction(function () use ($invoices) {
|
||||
|
@ -152,19 +152,39 @@ class NinjaMailerJob implements ShouldQueue
|
||||
LightLogs::create(new EmailSuccess($this->nmo->company->company_key, $this->nmo->mailable->subject))
|
||||
->send();
|
||||
|
||||
} catch (\Symfony\Component\Mime\Exception\RfcComplianceException $e) {
|
||||
}
|
||||
catch (\Symfony\Component\Mime\Exception\RfcComplianceException $e) {
|
||||
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
|
||||
$this->fail();
|
||||
$this->cleanUpMailers();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
return;
|
||||
} catch (\Symfony\Component\Mime\Exception\LogicException $e) {
|
||||
}
|
||||
catch (\Symfony\Component\Mime\Exception\LogicException $e) {
|
||||
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
|
||||
$this->fail();
|
||||
$this->cleanUpMailers();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
return;
|
||||
} catch (\Exception | \Google\Service\Exception $e) {
|
||||
}
|
||||
catch(\Symfony\Component\Mailer\Transport\Dsn $e){
|
||||
nlog("Incorrectly configured mail server - setting to default mail driver.");
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
}
|
||||
catch(\Google\Service\Exception $e){
|
||||
|
||||
if ($e->getCode() == '429') {
|
||||
|
||||
$message = "Google rate limiting triggered, we are queueing based on Gmail requirements.";
|
||||
$this->logMailError($message, $this->company->clients()->first());
|
||||
sleep(rand(1, 2));
|
||||
$this->release(900);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
nlog("Mailer failed with {$e->getMessage()}");
|
||||
$message = $e->getMessage();
|
||||
|
||||
@ -221,8 +241,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
}
|
||||
|
||||
/* Releasing immediately does not add in the backoff */
|
||||
sleep(rand(5, 10));
|
||||
|
||||
sleep(rand(2, 3));
|
||||
$this->release($this->backoff()[$this->attempts() - 1]);
|
||||
}
|
||||
|
||||
@ -779,15 +798,20 @@ class NinjaMailerJob implements ShouldQueue
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = json_decode($guzzle->post($url, [
|
||||
'form_params' => [
|
||||
'client_id' => config('ninja.o365.client_id'),
|
||||
'client_secret' => config('ninja.o365.client_secret'),
|
||||
'scope' => 'email Mail.Send offline_access profile User.Read openid',
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $user->oauth_user_refresh_token
|
||||
],
|
||||
])->getBody()->getContents());
|
||||
try {
|
||||
$token = json_decode($guzzle->post($url, [
|
||||
'form_params' => [
|
||||
'client_id' => config('ninja.o365.client_id'),
|
||||
'client_secret' => config('ninja.o365.client_secret'),
|
||||
'scope' => 'email Mail.Send offline_access profile User.Read openid',
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $user->oauth_user_refresh_token
|
||||
],
|
||||
])->getBody()->getContents());
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("Problem getting new Microsoft token for User: {$user->email}");
|
||||
}
|
||||
|
||||
if ($token) {
|
||||
$user->oauth_user_refresh_token = property_exists($token, 'refresh_token') ? $token->refresh_token : $user->oauth_user_refresh_token;
|
||||
|
@ -17,6 +17,7 @@ use App\Models\User;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateUser
|
||||
{
|
||||
@ -62,6 +63,7 @@ class CreateUser
|
||||
$user->fill($this->request);
|
||||
$user->email = $this->request['email']; //todo need to remove this in production
|
||||
$user->last_login = now();
|
||||
$user->referral_code = Str::lower(Str::random(32));
|
||||
$user->ip = request()->ip();
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
|
@ -1,215 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\DataMapper\FeesAndLimits;
|
||||
use App\Factory\CompanyGatewayFactory;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\User;
|
||||
use App\PaymentDrivers\WePayPaymentDriver;
|
||||
use Livewire\Component;
|
||||
use WePay;
|
||||
|
||||
class WepaySignup extends Component
|
||||
{
|
||||
public $user;
|
||||
|
||||
public $user_id;
|
||||
|
||||
public $company_key;
|
||||
|
||||
public $first_name;
|
||||
|
||||
public $last_name;
|
||||
|
||||
public $email;
|
||||
|
||||
public $company_name;
|
||||
|
||||
public $country;
|
||||
|
||||
public $ach;
|
||||
|
||||
public $wepay_payment_tos_agree;
|
||||
|
||||
public $debit_cards;
|
||||
|
||||
public $terms;
|
||||
|
||||
public $privacy_policy;
|
||||
|
||||
public $saved;
|
||||
|
||||
public Company $company;
|
||||
|
||||
protected $rules = [
|
||||
'first_name' => ['required'],
|
||||
'last_name' => ['required'],
|
||||
'email' => ['required', 'email'],
|
||||
'company_name' => ['required'],
|
||||
'country' => ['required'],
|
||||
'ach' => ['sometimes'],
|
||||
'wepay_payment_tos_agree' => ['accepted'],
|
||||
'debit_cards' => ['sometimes'],
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$user = User::find($this->user_id);
|
||||
$this->company = Company::query()->where('company_key', $this->company->company_key)->first();
|
||||
|
||||
$this->fill([
|
||||
'wepay_payment_tos_agree' => '',
|
||||
'ach' => '',
|
||||
'country' => 'US',
|
||||
'user' => $user,
|
||||
'first_name' => $user->first_name,
|
||||
'last_name' => $user->last_name,
|
||||
'email' => $user->email,
|
||||
'company_name' => $this->company->present()->name(),
|
||||
'saved' => ctrans('texts.confirm'),
|
||||
'terms' => '<a href="https://go.wepay.com/terms-of-service" target="_blank">'.ctrans('texts.terms_of_service').'</a>',
|
||||
'privacy_policy' => '<a href="https://go.wepay.com/privacy-policy" target="_blank">'.ctrans('texts.privacy_policy').'</a>',
|
||||
]);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.wepay.signup.wepay-signup');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$data = $this->validate($this->rules);
|
||||
|
||||
//need to create or get a new WePay CompanyGateway
|
||||
$cg = CompanyGateway::query()->where('gateway_key', '8fdeed552015b3c7b44ed6c8ebd9e992')
|
||||
->where('company_id', $this->company->id)
|
||||
->firstOrNew();
|
||||
|
||||
if (! $cg->id) {
|
||||
$fees_and_limits = new \stdClass();
|
||||
$fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits();
|
||||
$fees_and_limits->{GatewayType::BANK_TRANSFER} = new FeesAndLimits();
|
||||
|
||||
$cg = CompanyGatewayFactory::create($this->company->id, $this->user->id);
|
||||
$cg->gateway_key = '8fdeed552015b3c7b44ed6c8ebd9e992';
|
||||
$cg->require_cvv = false;
|
||||
$cg->require_billing_address = false;
|
||||
$cg->require_shipping_address = false;
|
||||
$cg->update_details = false;
|
||||
$cg->config = encrypt(config('ninja.testvars.checkout'));
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->token_billing = 'always';
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
$this->saved = ctrans('texts.processing');
|
||||
|
||||
$wepay_driver = new WePayPaymentDriver($cg, null, null);
|
||||
|
||||
$wepay = $wepay_driver->init()->wepay;
|
||||
|
||||
$user_details = [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'email' => $data['email'],
|
||||
'first_name' => $data['first_name'],
|
||||
'last_name' => $data['last_name'],
|
||||
'original_ip' => request()->ip(),
|
||||
'original_device' => request()->server('HTTP_USER_AGENT'),
|
||||
'tos_acceptance_time' => time(),
|
||||
'redirect_uri' => route('wepay.finished'),
|
||||
'scope' => 'manage_accounts,collect_payments,view_user,preapprove_payments,send_money',
|
||||
];
|
||||
|
||||
$wepay_user = $wepay->request('user/register/', $user_details);
|
||||
|
||||
$access_token = $wepay_user->access_token;
|
||||
|
||||
$access_token_expires = $wepay_user->expires_in ? (time() + $wepay_user->expires_in) : null;
|
||||
|
||||
$wepay = new WePay($access_token);
|
||||
|
||||
$account_details = [
|
||||
'name' => $data['company_name'],
|
||||
'description' => ctrans('texts.wepay_account_description'),
|
||||
'theme_object' => json_decode('{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}'),
|
||||
'callback_uri' => route('payment_webhook', ['company_key' => $this->company->company_key, 'company_gateway_id' => $cg->hashed_id]),
|
||||
'rbits' => $this->company->rBits(),
|
||||
'country' => $data['country'],
|
||||
];
|
||||
|
||||
if ($data['country'] == 'CA') {
|
||||
$account_details['currencies'] = ['CAD'];
|
||||
$account_details['country_options'] = ['debit_opt_in' => boolval($data['debit_cards'])];
|
||||
} elseif ($data['country'] == 'GB') {
|
||||
$account_details['currencies'] = ['GBP'];
|
||||
}
|
||||
|
||||
$wepay_account = $wepay->request('account/create/', $account_details);
|
||||
$confirmation_required = false;
|
||||
|
||||
try {
|
||||
$wepay->request('user/send_confirmation/', []);
|
||||
$confirmation_required = true;
|
||||
} catch (\WePayException $ex) {
|
||||
if ($ex->getMessage() == 'This access_token is already approved.') {
|
||||
$confirmation_required = false;
|
||||
} else {
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
request()->session()->flash('message', $ex->getMessage());
|
||||
}
|
||||
|
||||
nlog('failed in try catch ');
|
||||
nlog($ex->getMessage());
|
||||
}
|
||||
|
||||
$config = [
|
||||
'userId' => $wepay_user->user_id,
|
||||
'accessToken' => $access_token,
|
||||
'tokenType' => $wepay_user->token_type,
|
||||
'tokenExpires' => $access_token_expires,
|
||||
'accountId' => $wepay_account->account_id,
|
||||
'state' => $wepay_account->state,
|
||||
'testMode' => config('ninja.wepay.environment') == 'staging',
|
||||
'country' => $data['country'],
|
||||
];
|
||||
|
||||
$cg->setConfig($config);
|
||||
$cg->save();
|
||||
|
||||
if ($confirmation_required) {
|
||||
|
||||
/** @phpstan-ignore-next-line **/
|
||||
request()->session()->flash('message', trans('texts.created_wepay_confirmation_required'));
|
||||
} else {
|
||||
$update_uri = $wepay->request('/account/get_update_uri', [
|
||||
'account_id' => $wepay_account->account_id,
|
||||
'redirect_uri' => config('ninja.app_url'),
|
||||
]);
|
||||
|
||||
return redirect($update_uri->uri);
|
||||
}
|
||||
|
||||
return redirect()->to('/wepay/finished');
|
||||
}
|
||||
}
|
@ -44,4 +44,15 @@ class ClientContactPresenter extends EntityPresenter
|
||||
{
|
||||
return $this->name().' <'.$this->entity->email.'>' ?? '';
|
||||
}
|
||||
|
||||
public function phone()
|
||||
{
|
||||
return strlen($this->phone ?? '') > 1 ? $this->phone : '';
|
||||
}
|
||||
|
||||
public function email()
|
||||
{
|
||||
return strlen($this->email ?? '') > 1 ? $this->email : '';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Notifications\Ninja;
|
||||
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class WePayFailureNotification extends Notification
|
||||
{
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected $company_id;
|
||||
|
||||
public function __construct($company_id)
|
||||
{
|
||||
$this->company_id = $company_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['slack'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
*
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public function toSlack($notifiable)
|
||||
{
|
||||
$ip = '';
|
||||
|
||||
if (request()) {
|
||||
$ip = request()->getClientIp();
|
||||
}
|
||||
|
||||
return (new SlackMessage())
|
||||
->success()
|
||||
->from(ctrans('texts.notification_bot'))
|
||||
->image('https://app.invoiceninja.com/favicon.png')
|
||||
->content("New WePay ACH Failure from Company ID: {$this->company_id} IP: {$ip}");
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@
|
||||
@include('portal.ninja2020.gateways.includes.save_card')
|
||||
|
||||
<!-- This include pops up a credit card form -->
|
||||
@include('portal.ninja2020.gateways.wepay.includes.credit_card')
|
||||
@include('portal.ninja2020.gateways.stripe.includes.credit_card')
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.pay_now')
|
||||
|
||||
|
@ -1,354 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\WePay;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SystemLog;
|
||||
use App\Notifications\Ninja\WePayFailureNotification;
|
||||
use App\PaymentDrivers\WePayPaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ACH
|
||||
{
|
||||
use MakesHash;
|
||||
use WePayCommon;
|
||||
|
||||
public $wepay_payment_driver;
|
||||
|
||||
public function __construct(WePayPaymentDriver $wepay_payment_driver)
|
||||
{
|
||||
$this->wepay_payment_driver = $wepay_payment_driver;
|
||||
}
|
||||
|
||||
public function authorizeView($data)
|
||||
{
|
||||
$data['gateway'] = $this->wepay_payment_driver;
|
||||
$data['country_code'] = $this->wepay_payment_driver?->client?->country ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company->country()->iso_3166_2;
|
||||
|
||||
return render('gateways.wepay.authorize.bank_transfer', $data);
|
||||
}
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
//https://developer.wepay.com/api/api-calls/credit_card#authorize
|
||||
$data = $request->all();
|
||||
// authorize the credit card
|
||||
|
||||
//nlog($data);
|
||||
/*
|
||||
'_token' => '1Fk5CRj34up5ntKPvrFyMIAJhDdUNF3boqT3iIN3',
|
||||
'company_gateway_id' => '39',
|
||||
'payment_method_id' => '1',
|
||||
'gateway_response' => NULL,
|
||||
'is_default' => NULL,
|
||||
'credit_card_id' => '180642154638',
|
||||
'q' => '/client/payment_methods',
|
||||
'method' => '1',
|
||||
*/
|
||||
|
||||
try {
|
||||
$response = $this->wepay_payment_driver->wepay->request('payment_bank/persist', [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'payment_bank_id' => (int) $data['bank_account_id'],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->wepay_payment_driver->sendFailureMail($e->getMessage());
|
||||
|
||||
$message = [
|
||||
'server_response' => $e->getMessage(),
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_WEPAY,
|
||||
$this->wepay_payment_driver->client,
|
||||
$this->wepay_payment_driver->client->company,
|
||||
);
|
||||
|
||||
if (config('ninja.notification.slack')) {
|
||||
$this->wepay_payment_driver->company_gateway->company->notification(new WePayFailureNotification($this->wepay_payment_driver->company_gateway->company))->ninja();
|
||||
}
|
||||
|
||||
throw new PaymentFailed($e->getMessage(), 400);
|
||||
}
|
||||
// display the response
|
||||
// nlog($response);
|
||||
|
||||
if (in_array($response->state, ['new', 'pending', 'authorized'])) {
|
||||
$this->storePaymentMethod($response, GatewayType::BANK_TRANSFER);
|
||||
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
throw new PaymentFailed('There was a problem adding this payment method.', 400);
|
||||
/*
|
||||
{
|
||||
"payment_bank_id": 12345,
|
||||
"bank_name": "Wells Fargo",
|
||||
"account_last_four": "6789",
|
||||
"state": "authorized"
|
||||
}
|
||||
|
||||
state options: new, pending, authorized, disabled.
|
||||
*/
|
||||
}
|
||||
|
||||
/* If the bank transfer token is PENDING - we need to verify!! */
|
||||
//
|
||||
|
||||
public function verificationView(ClientGatewayToken $token)
|
||||
{
|
||||
$this->wepay_payment_driver->init();
|
||||
|
||||
$data = [
|
||||
'token' => $token,
|
||||
'gateway' => $this->wepay_payment_driver,
|
||||
];
|
||||
|
||||
return render('gateways.wepay.authorize.verify', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
{
|
||||
"client_id": 1234,
|
||||
"client_secret": "b1fc2f68-4d1f-4a",
|
||||
"payment_bank_id": 12345,
|
||||
"type": "microdeposits",
|
||||
"microdeposits": [
|
||||
8,
|
||||
12
|
||||
]
|
||||
}
|
||||
*/
|
||||
public function processVerification(Request $request, ClientGatewayToken $token)
|
||||
{
|
||||
$transactions = $request->input('transactions');
|
||||
|
||||
$transformed_transactions = [];
|
||||
|
||||
foreach ($transactions as $transaction) {
|
||||
$transformed_transactions[] = (int) $transaction;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->wepay_payment_driver->wepay->request('payment_bank/verify', [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'payment_bank_id' => $token->token,
|
||||
'type' => 'microdeposits',
|
||||
'microdeposits' => $transformed_transactions,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
nlog('we pay exception');
|
||||
nlog($e->getMessage());
|
||||
|
||||
return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER])
|
||||
->with('error', $e->getMessage());
|
||||
}
|
||||
/*
|
||||
{
|
||||
"payment_bank_id": 12345,
|
||||
"bank_name": "Wells Fargo",
|
||||
"account_last_four": "6789",
|
||||
"state": "authorized"
|
||||
}
|
||||
*/
|
||||
nlog($response);
|
||||
|
||||
//$meta = $token->meta;
|
||||
if ($response->state == 'authorized') {
|
||||
$meta = $token->meta;
|
||||
$meta->state = $response->state;
|
||||
$token->meta = $meta;
|
||||
$token->save();
|
||||
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
} else {
|
||||
return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER])
|
||||
->with('error', ctrans('texts.verification_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$data['gateway'] = $this->wepay_payment_driver;
|
||||
$data['currency'] = $this->wepay_payment_driver->client->getCurrencyCode();
|
||||
$data['payment_method_id'] = GatewayType::BANK_TRANSFER;
|
||||
$data['amount'] = $data['total']['amount_with_fee'];
|
||||
|
||||
return render('gateways.wepay.bank_transfer', $data);
|
||||
}
|
||||
|
||||
public function paymentResponse($request)
|
||||
{
|
||||
$token = ClientGatewayToken::query()->find($this->decodePrimaryKey($request->input('source')));
|
||||
$token_meta = $token->meta;
|
||||
|
||||
if (! property_exists($token_meta, 'state') || $token_meta->state != 'authorized') {
|
||||
$response = $this->wepay_payment_driver->wepay->request('/payment_bank', [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'payment_bank_id' => $token->token,
|
||||
]);
|
||||
|
||||
if ($response->state == 'authorized') {
|
||||
$meta = $token->meta;
|
||||
$meta->state = $response->state;
|
||||
$token->meta = $meta;
|
||||
$token->save();
|
||||
} else {
|
||||
return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]);
|
||||
}
|
||||
}
|
||||
|
||||
$app_fee = (config('ninja.wepay.fee_ach_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed');
|
||||
|
||||
try {
|
||||
$response = $this->wepay_payment_driver->wepay->request('checkout/create', [
|
||||
// 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]),
|
||||
'unique_id' => Str::random(40),
|
||||
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
|
||||
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
|
||||
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
|
||||
'short_description' => 'Goods and Services',
|
||||
'type' => 'goods',
|
||||
'fee' => [
|
||||
'fee_payer' => config('ninja.wepay.fee_payer'),
|
||||
'app_fee' => $app_fee,
|
||||
],
|
||||
'payment_method' => [
|
||||
'type' => 'payment_bank',
|
||||
'payment_bank' => [
|
||||
'id' => $token->token,
|
||||
],
|
||||
],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
throw new PaymentFailed($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
/* Merge all data and store in the payment hash*/
|
||||
$state = [
|
||||
'server_response' => $response,
|
||||
'payment_hash' => $request->payment_hash,
|
||||
];
|
||||
|
||||
$state = array_merge($state, $request->all());
|
||||
$this->wepay_payment_driver->payment_hash->data = array_merge((array) $this->wepay_payment_driver->payment_hash->data, $state);
|
||||
$this->wepay_payment_driver->payment_hash->save();
|
||||
|
||||
if (in_array($response->state, ['authorized', 'captured'])) {
|
||||
//success
|
||||
nlog('success');
|
||||
$payment_status = $response->state == 'authorized' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING;
|
||||
|
||||
return $this->processSuccessfulPayment($response, $payment_status, GatewayType::BANK_TRANSFER);
|
||||
}
|
||||
|
||||
if (in_array($response->state, ['released', 'cancelled', 'failed', 'expired'])) {
|
||||
//some type of failure
|
||||
nlog('failure');
|
||||
|
||||
$payment_status = $response->state == 'cancelled' ? Payment::STATUS_CANCELLED : Payment::STATUS_FAILED;
|
||||
|
||||
$this->processUnSuccessfulPayment($response, $payment_status);
|
||||
}
|
||||
}
|
||||
|
||||
private function storePaymentMethod($response, $payment_method_id)
|
||||
{
|
||||
$payment_meta = new \stdClass();
|
||||
$payment_meta->exp_month = (string) '';
|
||||
$payment_meta->exp_year = (string) '';
|
||||
$payment_meta->brand = (string) $response->bank_name;
|
||||
$payment_meta->last4 = (string) $response->account_last_four;
|
||||
$payment_meta->type = GatewayType::BANK_TRANSFER;
|
||||
$payment_meta->state = $response->state;
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $response->payment_bank_id,
|
||||
'payment_method_id' => $payment_method_id,
|
||||
];
|
||||
|
||||
$this->wepay_payment_driver->storeGatewayToken($data);
|
||||
}
|
||||
|
||||
public function tokenBilling($token, $payment_hash)
|
||||
{
|
||||
$token_meta = $token->meta;
|
||||
|
||||
if (! property_exists($token_meta, 'state') || $token_meta->state != 'authorized') {
|
||||
return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]);
|
||||
}
|
||||
|
||||
$amount = array_sum(array_column($this->wepay_payment_driver->payment_hash->invoices(), 'amount')) + $this->wepay_payment_driver->payment_hash->fee_total;
|
||||
|
||||
$app_fee = (config('ninja.wepay.fee_cc_multiplier') * $amount) + config('ninja.wepay.fee_fixed');
|
||||
|
||||
$response = $this->wepay_payment_driver->wepay->request('checkout/create', [
|
||||
'unique_id' => Str::random(40),
|
||||
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
|
||||
'amount' => $amount,
|
||||
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
|
||||
'short_description' => 'Goods and Services',
|
||||
'type' => 'goods',
|
||||
'fee' => [
|
||||
'fee_payer' => config('ninja.wepay.fee_payer'),
|
||||
'app_fee' => $app_fee,
|
||||
],
|
||||
'payment_method' => [
|
||||
'type' => 'payment_bank',
|
||||
'payment_bank' => [
|
||||
'id' => $token->token,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
/* Merge all data and store in the payment hash*/
|
||||
$state = [
|
||||
'server_response' => $response,
|
||||
'payment_hash' => $this->wepay_payment_driver->payment_hash,
|
||||
];
|
||||
|
||||
$this->wepay_payment_driver->payment_hash->data = array_merge((array) $this->wepay_payment_driver->payment_hash->data, $state);
|
||||
$this->wepay_payment_driver->payment_hash->save();
|
||||
|
||||
if (in_array($response->state, ['authorized', 'captured'])) {
|
||||
//success
|
||||
nlog('success');
|
||||
$payment_status = $response->state == 'authorized' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING;
|
||||
|
||||
return $this->processSuccessfulPayment($response, $payment_status, GatewayType::BANK_TRANSFER, true);
|
||||
}
|
||||
|
||||
if (in_array($response->state, ['released', 'cancelled', 'failed', 'expired'])) {
|
||||
//some type of failure
|
||||
nlog('failure');
|
||||
|
||||
$payment_status = $response->state == 'cancelled' ? Payment::STATUS_CANCELLED : Payment::STATUS_FAILED;
|
||||
|
||||
$this->processUnSuccessfulPayment($response, $payment_status);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,346 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\WePay;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\WePayPaymentDriver;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreditCard
|
||||
{
|
||||
use WePayCommon;
|
||||
|
||||
public $wepay_payment_driver;
|
||||
|
||||
public function __construct(WePayPaymentDriver $wepay_payment_driver)
|
||||
{
|
||||
$this->wepay_payment_driver = $wepay_payment_driver;
|
||||
}
|
||||
|
||||
public function authorizeView($data)
|
||||
{
|
||||
$data['gateway'] = $this->wepay_payment_driver;
|
||||
$data['country_code'] = $this->wepay_payment_driver?->client?->country ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company->country()->iso_3166_2;
|
||||
|
||||
return render('gateways.wepay.authorize.authorize', $data);
|
||||
}
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
//https://developer.wepay.com/api/api-calls/credit_card#authorize
|
||||
$data = $request->all();
|
||||
// authorize the credit card
|
||||
|
||||
// nlog($data);
|
||||
/*
|
||||
'_token' => '1Fk5CRj34up5ntKPvrFyMIAJhDdUNF3boqT3iIN3',
|
||||
'company_gateway_id' => '39',
|
||||
'payment_method_id' => '1',
|
||||
'gateway_response' => NULL,
|
||||
'is_default' => NULL,
|
||||
'credit_card_id' => '180642154638',
|
||||
'q' => '/client/payment_methods',
|
||||
'method' => '1',
|
||||
*/
|
||||
|
||||
try {
|
||||
$response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'credit_card_id' => (int) $data['credit_card_id'],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->wepay_payment_driver->processInternallyFailedPayment($this->wepay_payment_driver, $e);
|
||||
}
|
||||
// display the response
|
||||
// nlog($response);
|
||||
|
||||
if (in_array($response->state, ['new', 'authorized'])) {
|
||||
$this->storePaymentMethod($response, GatewayType::CREDIT_CARD);
|
||||
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
throw new PaymentFailed('There was a problem adding this payment method.', 400);
|
||||
/*
|
||||
[credit_card_id] => 348084962473
|
||||
[credit_card_name] => Visa xxxxxx4018
|
||||
[state] => authorized
|
||||
[user_name] => Joey Diaz
|
||||
[email] => user@example.com
|
||||
[create_time] => 1623798172
|
||||
[expiration_month] => 10
|
||||
[expiration_year] => 2023
|
||||
[last_four] => 4018
|
||||
[input_source] => card_keyed
|
||||
[virtual_terminal_mode] => none
|
||||
[card_on_file] =>
|
||||
[recurring] =>
|
||||
[cvv_provided] => 1
|
||||
[auto_update] =>
|
||||
*/
|
||||
}
|
||||
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$data['gateway'] = $this->wepay_payment_driver;
|
||||
$data['description'] = ctrans('texts.invoices').': '.collect($data['invoices'])->pluck('invoice_number');
|
||||
$data['country_code'] = $this->wepay_payment_driver?->client?->country ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2;
|
||||
|
||||
return render('gateways.wepay.credit_card.pay', $data);
|
||||
}
|
||||
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
nlog('payment response');
|
||||
|
||||
//it could be an existing token or a new credit_card_id that needs to be converted into a wepay token
|
||||
if ($request->has('credit_card_id') && $request->input('credit_card_id')) {
|
||||
nlog('authorize the card first!');
|
||||
|
||||
try {
|
||||
$response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'credit_card_id' => (int) $request->input('credit_card_id'),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->wepay_payment_driver->processInternallyFailedPayment($this->wepay_payment_driver, $e);
|
||||
}
|
||||
|
||||
$credit_card_id = (int) $response->credit_card_id;
|
||||
|
||||
if (in_array($response->state, ['new', 'authorized']) && boolval($request->input('store_card'))) {
|
||||
$this->storePaymentMethod($response, GatewayType::CREDIT_CARD);
|
||||
}
|
||||
} else {
|
||||
$credit_card_id = (int) $request->input('token');
|
||||
}
|
||||
|
||||
// USD, CAD, and GBP.
|
||||
// nlog($request->all());
|
||||
|
||||
$app_fee = (config('ninja.wepay.fee_cc_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed');
|
||||
// charge the credit card
|
||||
|
||||
try {
|
||||
$response = $this->wepay_payment_driver->wepay->request('checkout/create', [
|
||||
'unique_id' => Str::random(40),
|
||||
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
|
||||
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
|
||||
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
|
||||
'short_description' => 'Goods and services',
|
||||
'type' => 'goods',
|
||||
'fee' => [
|
||||
'fee_payer' => config('ninja.wepay.fee_payer'),
|
||||
'app_fee' => $app_fee,
|
||||
],
|
||||
'payment_method' => [
|
||||
'type' => 'credit_card',
|
||||
'credit_card' => [
|
||||
'id' => $credit_card_id,
|
||||
],
|
||||
],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->wepay_payment_driver->sendFailureMail($e->getMessage());
|
||||
|
||||
$message = [
|
||||
'server_response' => $e->getMessage(),
|
||||
'data' => $this->wepay_payment_driver->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_WEPAY,
|
||||
$this->wepay_payment_driver->client,
|
||||
$this->wepay_payment_driver->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
/* Merge all data and store in the payment hash*/
|
||||
$state = [
|
||||
'server_response' => $response,
|
||||
'payment_hash' => $request->payment_hash,
|
||||
];
|
||||
|
||||
$state = array_merge($state, $request->all());
|
||||
$this->wepay_payment_driver->payment_hash->data = array_merge((array) $this->wepay_payment_driver->payment_hash->data, $state);
|
||||
$this->wepay_payment_driver->payment_hash->save();
|
||||
|
||||
if (in_array($response->state, ['authorized', 'captured'])) {
|
||||
//success
|
||||
nlog('success');
|
||||
$payment_status = $response->state == 'authorized' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING;
|
||||
|
||||
return $this->processSuccessfulPayment($response, $payment_status, GatewayType::CREDIT_CARD);
|
||||
}
|
||||
|
||||
if (in_array($response->state, ['released', 'cancelled', 'failed', 'expired'])) {
|
||||
//some type of failure
|
||||
nlog('failure');
|
||||
|
||||
$payment_status = $response->state == 'cancelled' ? Payment::STATUS_CANCELLED : Payment::STATUS_FAILED;
|
||||
|
||||
$this->processUnSuccessfulPayment($response, $payment_status);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
new The checkout was created by the application. This state typically indicates that checkouts created in WePay's hosted checkout flow are waiting for the payer to submit their information.
|
||||
authorized The payer entered their payment info and confirmed the payment on WePay. WePay has successfully charged the card.
|
||||
captured The payment has been reserved from the payer.
|
||||
released The payment has been credited to the payee account. Note that the released state may be active although there are active partial refunds or partial chargebacks.
|
||||
cancelled The payment has been cancelled by the payer, payee, or application.
|
||||
refunded The payment was captured and then refunded by the payer, payee, or application. The payment has been debited from the payee account.
|
||||
charged back The payment has been charged back by the payer and the payment has been debited from the payee account.
|
||||
failed The payment has failed.
|
||||
expired Checkouts expire if they remain in the new state for more than 30 minutes (e.g., they have been abandoned).
|
||||
*/
|
||||
|
||||
/*
|
||||
https://developer.wepay.com/api/api-calls/checkout
|
||||
{
|
||||
"checkout_id": 649945633,
|
||||
"account_id": 1548718026,
|
||||
"type": "donation",
|
||||
"short_description": "test checkout",
|
||||
"currency": "USD",
|
||||
"amount": 20,
|
||||
"state": "authorized",
|
||||
"soft_descriptor": "WPY*Wolverine",
|
||||
"auto_release": true,
|
||||
"create_time": 1463589958,
|
||||
"gross": 20.88,
|
||||
"reference_id": null,
|
||||
"callback_uri": null,
|
||||
"long_description": null,
|
||||
"delivery_type": null,
|
||||
"initiated_by": "merchant",
|
||||
"in_review": false,
|
||||
"fee": {
|
||||
"app_fee": 0,
|
||||
"processing_fee": 0.88,
|
||||
"fee_payer": "payer"
|
||||
},
|
||||
"chargeback": {
|
||||
"amount_charged_back": 0,
|
||||
"dispute_uri": null
|
||||
},
|
||||
"refund": {
|
||||
"amount_refunded": 0,
|
||||
"refund_reason": null
|
||||
},
|
||||
"payment_method": {
|
||||
"type": "credit_card",
|
||||
"credit_card": {
|
||||
"id": 1684847614,
|
||||
"data": {
|
||||
"emv_receipt": null,
|
||||
"signature_url": null
|
||||
},
|
||||
"auto_release": false
|
||||
}
|
||||
},
|
||||
"hosted_checkout": null,
|
||||
"payer": {
|
||||
"email": "test@example.com",
|
||||
"name": "Mr Smith",
|
||||
"home_address": null
|
||||
},
|
||||
"npo_information": null,
|
||||
"payment_error": null
|
||||
}
|
||||
*/
|
||||
|
||||
private function storePaymentMethod($response, $payment_method_id)
|
||||
{
|
||||
nlog('storing card');
|
||||
|
||||
$payment_meta = new \stdClass();
|
||||
$payment_meta->exp_month = (string) $response->expiration_month;
|
||||
$payment_meta->exp_year = (string) $response->expiration_year;
|
||||
$payment_meta->brand = (string) $response->credit_card_name;
|
||||
$payment_meta->last4 = (string) $response->last_four;
|
||||
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $response->credit_card_id,
|
||||
'payment_method_id' => $payment_method_id,
|
||||
];
|
||||
|
||||
$this->wepay_payment_driver->storeGatewayToken($data);
|
||||
}
|
||||
|
||||
public function tokenBilling($cgt, $payment_hash)
|
||||
{
|
||||
$amount = array_sum(array_column($this->wepay_payment_driver->payment_hash->invoices(), 'amount')) + $this->wepay_payment_driver->payment_hash->fee_total;
|
||||
|
||||
$app_fee = (config('ninja.wepay.fee_cc_multiplier') * $amount) + config('ninja.wepay.fee_fixed');
|
||||
// charge the credit card
|
||||
$response = $this->wepay_payment_driver->wepay->request('checkout/create', [
|
||||
'unique_id' => Str::random(40),
|
||||
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
|
||||
'amount' => $amount,
|
||||
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
|
||||
'short_description' => 'Goods and services',
|
||||
'type' => 'goods',
|
||||
'fee' => [
|
||||
'fee_payer' => config('ninja.wepay.fee_payer'),
|
||||
'app_fee' => $app_fee,
|
||||
],
|
||||
'payment_method' => [
|
||||
'type' => 'credit_card',
|
||||
'credit_card' => [
|
||||
'id' => $cgt->token,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
/* Merge all data and store in the payment hash*/
|
||||
$state = [
|
||||
'server_response' => $response,
|
||||
'payment_hash' => $payment_hash,
|
||||
];
|
||||
|
||||
$this->wepay_payment_driver->payment_hash->data = array_merge((array) $this->wepay_payment_driver->payment_hash->data, $state);
|
||||
$this->wepay_payment_driver->payment_hash->save();
|
||||
|
||||
if (in_array($response->state, ['authorized', 'captured'])) {
|
||||
//success
|
||||
nlog('success');
|
||||
$payment_status = $response->state == 'authorized' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING;
|
||||
|
||||
return $this->processSuccessfulPayment($response, $payment_status, GatewayType::CREDIT_CARD, true);
|
||||
}
|
||||
|
||||
if (in_array($response->state, ['released', 'cancelled', 'failed', 'expired'])) {
|
||||
//some type of failure
|
||||
nlog('failure');
|
||||
|
||||
$payment_status = $response->state == 'cancelled' ? Payment::STATUS_CANCELLED : Payment::STATUS_FAILED;
|
||||
|
||||
$this->processUnSuccessfulPayment($response, $payment_status);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\WePay;
|
||||
|
||||
use App\PaymentDrivers\WePayPaymentDriver;
|
||||
|
||||
class Setup
|
||||
{
|
||||
public $wepay;
|
||||
|
||||
public function __construct(WePayPaymentDriver $wepay)
|
||||
{
|
||||
$this->wepay = $wepay;
|
||||
}
|
||||
|
||||
public function boot($data)
|
||||
{
|
||||
/*
|
||||
'user_id',
|
||||
'user_company',
|
||||
*/
|
||||
|
||||
return render('gateways.wepay.signup.index', $data);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\WePay;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
|
||||
trait WePayCommon
|
||||
{
|
||||
private function processSuccessfulPayment($response, $payment_status, $gateway_type, $return_payment = false)
|
||||
{
|
||||
if ($gateway_type == GatewayType::BANK_TRANSFER) {
|
||||
$payment_type = PaymentType::ACH;
|
||||
} else {
|
||||
$payment_type = PaymentType::CREDIT_CARD_OTHER;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'payment_type' => $payment_type,
|
||||
'amount' => $response->amount,
|
||||
'transaction_reference' => $response->checkout_id,
|
||||
'gateway_type_id' => $gateway_type,
|
||||
];
|
||||
|
||||
$payment = $this->wepay_payment_driver->createPayment($data, $payment_status);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $this->wepay_payment_driver->payment_hash->data->server_response, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_WEPAY,
|
||||
$this->wepay_payment_driver->client,
|
||||
$this->wepay_payment_driver->client->company,
|
||||
);
|
||||
|
||||
if ($return_payment) {
|
||||
return $payment;
|
||||
}
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->wepay_payment_driver->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
||||
private function processUnSuccessfulPayment($response, $payment_status)
|
||||
{
|
||||
$this->wepay_payment_driver->sendFailureMail($response->state);
|
||||
|
||||
$message = [
|
||||
'server_response' => $response,
|
||||
'data' => $this->wepay_payment_driver->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_WEPAY,
|
||||
$this->wepay_payment_driver->client,
|
||||
$this->wepay_payment_driver->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed('Failed to process the payment.', 500);
|
||||
}
|
||||
}
|
@ -17,13 +17,12 @@ use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\WePay\ACH;
|
||||
use App\PaymentDrivers\WePay\CreditCard;
|
||||
use App\PaymentDrivers\WePay\Setup;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use WePay;
|
||||
|
||||
/**
|
||||
* @deprecated 5.9
|
||||
*/
|
||||
class WePayPaymentDriver extends BaseDriver
|
||||
{
|
||||
use MakesHash;
|
||||
@ -45,27 +44,14 @@ class WePayPaymentDriver extends BaseDriver
|
||||
|
||||
/* Maps the Payment Gateway Type - to its implementation */
|
||||
public static $methods = [
|
||||
GatewayType::CREDIT_CARD => CreditCard::class,
|
||||
GatewayType::BANK_TRANSFER => ACH::class,
|
||||
];
|
||||
|
||||
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_WEPAY;
|
||||
|
||||
public function init()
|
||||
{
|
||||
if (WePay::getEnvironment() == 'none') {
|
||||
if (config('ninja.wepay.environment') == 'staging') {
|
||||
WePay::useStaging(config('ninja.wepay.client_id'), config('ninja.wepay.client_secret'));
|
||||
} else {
|
||||
WePay::useProduction(config('ninja.wepay.client_id'), config('ninja.wepay.client_secret'));
|
||||
}
|
||||
}
|
||||
throw new \Exception("Gateway no longer supported", 500);
|
||||
|
||||
if ($this->company_gateway) {
|
||||
$this->wepay = new WePay($this->company_gateway->getConfigField('accessToken'));
|
||||
} else {
|
||||
$this->wepay = new WePay(null);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -93,7 +79,6 @@ class WePayPaymentDriver extends BaseDriver
|
||||
*/
|
||||
public function setup(array $data)
|
||||
{
|
||||
return (new Setup($this))->boot($data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,140 +153,17 @@ class WePayPaymentDriver extends BaseDriver
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$input = $request->all();
|
||||
|
||||
$config = $this->company_gateway->getConfig();
|
||||
|
||||
$accountId = $this->company_gateway->getConfigField('accountId');
|
||||
$objectId = false;
|
||||
$objectType = '';
|
||||
|
||||
foreach (array_keys($input) as $key) {
|
||||
if ('_id' == substr($key, -3)) {
|
||||
$objectType = substr($key, 0, -3);
|
||||
$objectId = $input[$key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $objectId) {
|
||||
throw new \Exception('Could not find object id parameter');
|
||||
}
|
||||
|
||||
if ($objectType == 'credit_card') {
|
||||
$payment_method = ClientGatewayToken::where('token', $objectId)->first();
|
||||
|
||||
if (! $payment_method) {
|
||||
throw new \Exception('Unknown payment method');
|
||||
}
|
||||
|
||||
$source = $this->wepay->request('credit_card', [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'credit_card_id' => (int) $objectId,
|
||||
]);
|
||||
|
||||
if ($source->state == 'deleted') {
|
||||
$payment_method->delete();
|
||||
} else {
|
||||
//$this->paymentService->convertPaymentMethodFromWePay($source, null, $paymentMethod)->save();
|
||||
}
|
||||
|
||||
return 'Processed successfully';
|
||||
} elseif ($objectType == 'account') {
|
||||
if ($accountId != $objectId) {
|
||||
throw new \Exception('Unknown account '.$accountId.' does not equal '.$objectId);
|
||||
}
|
||||
|
||||
$wepayAccount = $this->wepay->request('account', [
|
||||
'account_id' => (int) $objectId,
|
||||
]);
|
||||
|
||||
if ($wepayAccount->state == 'deleted') {
|
||||
$this->company_gateway->delete();
|
||||
} else {
|
||||
$config->state = $wepayAccount->state;
|
||||
$this->company_gateway->setConfig($config);
|
||||
$this->company_gateway->save();
|
||||
}
|
||||
|
||||
return ['message' => 'Processed successfully'];
|
||||
} elseif ($objectType == 'checkout') {
|
||||
/** @var \App\Models\Payment $payment */
|
||||
$payment = Payment::where('company_id', $this->company_gateway->company_id)
|
||||
->where('transaction_reference', '=', $objectId)
|
||||
->first();
|
||||
|
||||
if (! $payment) {
|
||||
throw new \Exception('Unknown payment');
|
||||
}
|
||||
|
||||
if ($payment->is_deleted) {
|
||||
throw new \Exception('Payment is deleted');
|
||||
}
|
||||
|
||||
$checkout = $this->wepay->request('checkout', [
|
||||
'checkout_id' => intval($objectId),
|
||||
]);
|
||||
|
||||
if ($checkout->state == 'captured') {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
} elseif ($checkout->state == 'cancelled') {
|
||||
$payment->service()->deletePayment()->save();
|
||||
} elseif ($checkout->state == 'failed') {
|
||||
$payment->status_id = Payment::STATUS_FAILED;
|
||||
$payment->save();
|
||||
}
|
||||
|
||||
return 'Processed successfully';
|
||||
} else {
|
||||
return 'Ignoring event';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function refund(Payment $payment, $amount, $return_client_response = false)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$response = $this->wepay->request('checkout/refund', [
|
||||
'checkout_id' => $payment->transaction_reference,
|
||||
'refund_reason' => 'Refund by merchant',
|
||||
'amount' => $amount,
|
||||
]);
|
||||
|
||||
return [
|
||||
'transaction_reference' => $response->checkout_id,
|
||||
'transaction_response' => json_encode($response),
|
||||
'success' => $response->state == 'refunded' ? true : false,
|
||||
'description' => 'refund',
|
||||
'code' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
public function detach(ClientGatewayToken $token)
|
||||
{
|
||||
/*Bank accounts cannot be deleted - only CC*/
|
||||
if ($token->gateway_type_id == 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->init();
|
||||
|
||||
$response = $this->wepay->request('/credit_card/delete', [
|
||||
'client_id' => config('ninja.wepay.client_id'),
|
||||
'client_secret' => config('ninja.wepay.client_secret'),
|
||||
'credit_card_id' => intval($token->token),
|
||||
]);
|
||||
|
||||
if ($response->state == 'deleted') {
|
||||
return true;
|
||||
} else {
|
||||
throw new \Exception(trans('texts.failed_remove_payment_method'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getClientRequiredFields(): array
|
||||
|
@ -1,4 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
|
@ -26,7 +26,6 @@ class BankTransactionRepository extends BaseRepository
|
||||
$bank_transaction->bank_integration_id = $data['bank_integration_id'];
|
||||
}
|
||||
|
||||
|
||||
$bank_transaction->fill($data);
|
||||
$bank_transaction->save();
|
||||
|
||||
@ -43,7 +42,7 @@ class BankTransactionRepository extends BaseRepository
|
||||
$data['transactions'] = $bank_transactions->map(function ($bt) {
|
||||
return ['id' => $bt->id, 'invoice_ids' => $bt->invoice_ids, 'ninja_category_id' => $bt->ninja_category_id];
|
||||
})->toArray();
|
||||
|
||||
|
||||
$bts = (new MatchBankTransactions($user->company()->id, $user->company()->db, $data))->handle();
|
||||
}
|
||||
|
||||
|
288
app/Services/EDocument/Standards/FatturaPANew.php
Normal file
288
app/Services/EDocument/Standards/FatturaPANew.php
Normal file
@ -0,0 +1,288 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\EDocument\Standards;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronica;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\IndirizzoType\Sede;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\AnagraficaType\Anagrafica;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\IdFiscaleType\IdFiscaleIVA;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\IdFiscaleType\IdTrasmittente;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DatiGeneraliType\DatiGenerali;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DatiPagamentoType\DatiPagamento;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DatiRiepilogoType\DatiRiepilogo;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DettaglioLineeType\DettaglioLinee;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DatiBeniServiziType\DatiBeniServizi;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DatiTrasmissioneType\DatiTrasmissione;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\CedentePrestatoreType\CedentePrestatore;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DatiAnagraficiCedenteType\DatiAnagrafici;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DettaglioPagamentoType\DettaglioPagamento;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\DatiGeneraliDocumentoType\DatiGeneraliDocumento;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\CessionarioCommittenteType\CessionarioCommittente;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronicaBodyType\FatturaElettronicaBody;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronicaHeaderType\FatturaElettronicaHeader;
|
||||
|
||||
class FatturaPANew extends AbstractService
|
||||
{
|
||||
private FatturaElettronica $FatturaElettronica;
|
||||
private FatturaElettronicaBody $FatturaElettronicaBody;
|
||||
private FatturaElettronicaHeader $FatturaElettronicaHeader;
|
||||
private DatiTrasmissione $DatiTrasmissione;
|
||||
private IdTrasmittente $IdTrasmittente;
|
||||
private CedentePrestatore $CedentePrestatore;
|
||||
private DatiAnagrafici $DatiAnagrafici;
|
||||
private IdFiscaleIVA $IdFiscaleIVA;
|
||||
private Anagrafica $Anagrafica;
|
||||
private DatiGeneraliDocumento $DatiGeneraliDocumento;
|
||||
private DatiGenerali $DatiGenerali;
|
||||
private DettaglioPagamento $DettaglioPagamento;
|
||||
/**
|
||||
* @param Invoice $invoice
|
||||
*/
|
||||
public function __construct(public Invoice $invoice)
|
||||
{
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->init()
|
||||
->setIdTrasmittente() //order of execution matters.
|
||||
->setDatiTrasmissione()
|
||||
->setIdFiscaleIVA()
|
||||
->setAnagrafica()
|
||||
->setDatiAnagrafici()
|
||||
->setCedentePrestatore()
|
||||
->setClientDetails()
|
||||
->setDatiGeneraliDocumento()
|
||||
->setDatiGenerali()
|
||||
->setLineItems()
|
||||
->setDettaglioPagamento()
|
||||
->setFatturaElettronica();
|
||||
}
|
||||
|
||||
public function getFatturaElettronica(): FatturaElettronica
|
||||
{
|
||||
return $this->FatturaElettronica;
|
||||
}
|
||||
|
||||
private function setDatiTrasmissione(): self
|
||||
{
|
||||
|
||||
$this->DatiTrasmissione->FormatoTrasmissione = "FPR12";
|
||||
$this->DatiTrasmissione->CodiceDestinatario = $this->invoice->client->routing_id;
|
||||
$this->DatiTrasmissione->ProgressivoInvio = $this->invoice->number;
|
||||
|
||||
$this->DatiTrasmissione->IdTrasmittente = $this->IdTrasmittente;
|
||||
|
||||
$this->FatturaElettronicaHeader->DatiTrasmissione = $this->DatiTrasmissione;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
private function setIdTrasmittente():self
|
||||
{
|
||||
$this->IdTrasmittente->IdPaese = $this->invoice->company->country()->iso_3166_2;
|
||||
$this->IdTrasmittente->IdCodice = $this->invoice->company->settings->vat_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setCedentePrestatore():self
|
||||
{
|
||||
$this->CedentePrestatore->DatiAnagrafici = $this->DatiAnagrafici;
|
||||
|
||||
$sede = new Sede;
|
||||
$sede->Indirizzo = $this->invoice->company->settings->address1;
|
||||
$sede->CAP = (int)$this->invoice->company->settings->postal_code;
|
||||
$sede->Comune = $this->invoice->company->settings->city;
|
||||
$sede->Provincia = $this->invoice->company->settings->state;
|
||||
$sede->Nazione = $this->invoice->company->country()->iso_3166_2;
|
||||
$this->CedentePrestatore->Sede = $sede;
|
||||
|
||||
$this->FatturaElettronicaHeader->CedentePrestatore = $this->CedentePrestatore;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setDatiAnagrafici():self
|
||||
{
|
||||
$this->DatiAnagrafici->RegimeFiscale = "RF01";
|
||||
$this->DatiAnagrafici->Anagrafica = $this->Anagrafica;
|
||||
$this->DatiAnagrafici->IdFiscaleIVA = $this->IdFiscaleIVA;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setClientDetails():self
|
||||
{
|
||||
|
||||
$datiAnagrafici = new DatiAnagrafici();
|
||||
$anagrafica = new Anagrafica();
|
||||
$anagrafica->Denominazione = $this->invoice->client->present()->name();
|
||||
$datiAnagrafici->Anagrafica = $anagrafica;
|
||||
|
||||
$idFiscale = new IdFiscaleIVA;
|
||||
$idFiscale->IdCodice= $this->invoice->client->vat_number;
|
||||
$idFiscale->IdPaese = $this->invoice->client->country->iso_3166_2;
|
||||
|
||||
$datiAnagrafici->IdFiscaleIVA = $idFiscale;
|
||||
|
||||
$sede = new Sede;
|
||||
$sede->Indirizzo = $this->invoice->client->address1;
|
||||
$sede->CAP = (int)$this->invoice->client->postal_code;
|
||||
$sede->Comune = $this->invoice->client->city;
|
||||
$sede->Provincia = $this->invoice->client->state;
|
||||
$sede->Nazione = $this->invoice->client->country->iso_3166_2;
|
||||
|
||||
$cessionarioCommittente = new CessionarioCommittente;
|
||||
$cessionarioCommittente->DatiAnagrafici = $datiAnagrafici;
|
||||
$cessionarioCommittente->Sede = $sede;
|
||||
|
||||
$this->FatturaElettronicaHeader->CessionarioCommittente = $cessionarioCommittente;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setIdFiscaleIVA():self
|
||||
{
|
||||
|
||||
$this->IdFiscaleIVA->IdPaese = $this->invoice->company->country()->iso_3166_2;
|
||||
$this->IdFiscaleIVA->IdCodice = $this->invoice->company->settings->vat_number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
//this is a choice, need to switch based on values here.
|
||||
private function setAnagrafica():self
|
||||
{
|
||||
$this->Anagrafica->Denominazione = $this->invoice->company->present()->name();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setDatiGeneraliDocumento():self
|
||||
{
|
||||
|
||||
$this->DatiGeneraliDocumento->TipoDocumento = "TD01";
|
||||
$this->DatiGeneraliDocumento->Divisa = $this->invoice->client->currency()->code;
|
||||
$this->DatiGeneraliDocumento->Data = new \DateTime($this->invoice->date);
|
||||
$this->DatiGeneraliDocumento->Numero = $this->invoice->number;
|
||||
$this->DatiGeneraliDocumento->Causale[] = substr($this->invoice->public_notes ?? '',0, 200); //unsure..
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setDatiGenerali():self
|
||||
{
|
||||
$this->DatiGenerali->DatiGeneraliDocumento = $this->DatiGeneraliDocumento;
|
||||
|
||||
$this->FatturaElettronicaBody->DatiGenerali = $this->DatiGenerali;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setDettaglioPagamento():self
|
||||
{
|
||||
|
||||
$this->DettaglioPagamento->ModalitaPagamento = "MP01"; //String
|
||||
$this->DettaglioPagamento->DataScadenzaPagamento = new \DateTime($this->invoice->due_date ?? $this->invoice->date);
|
||||
$this->DettaglioPagamento->ImportoPagamento = (string) sprintf('%0.2f', $this->invoice->balance);
|
||||
|
||||
$DatiPagamento = new DatiPagamento;
|
||||
$DatiPagamento->CondizioniPagamento = "TP02";
|
||||
$DatiPagamento->DettaglioPagamento[] = $this->DettaglioPagamento;
|
||||
|
||||
$this->FatturaElettronicaBody->DatiPagamento[] = $DatiPagamento;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setLineItems(): self
|
||||
{
|
||||
|
||||
$calc = $this->invoice->calc();
|
||||
|
||||
$datiBeniServizi = new DatiBeniServizi();
|
||||
$tax_rate_level = 0;
|
||||
//line items
|
||||
foreach ($this->invoice->line_items as $key => $item) {
|
||||
|
||||
$numero = $key + 1;
|
||||
$dettaglioLinee = new DettaglioLinee;
|
||||
$dettaglioLinee->NumeroLinea = "{$numero}";
|
||||
$dettaglioLinee->Descrizione = $item->notes ?? 'Descrizione';
|
||||
$dettaglioLinee->Quantita = sprintf('%0.2f', $item->quantity);
|
||||
$dettaglioLinee->PrezzoUnitario = sprintf('%0.2f', $item->cost);
|
||||
$dettaglioLinee->PrezzoTotale = sprintf('%0.2f', $item->line_total);
|
||||
$dettaglioLinee->AliquotaIVA = sprintf('%0.2f', $item->tax_rate1);
|
||||
|
||||
|
||||
$datiBeniServizi->DettaglioLinee[] = $dettaglioLinee;
|
||||
|
||||
if ($item->tax_rate1 > $tax_rate_level) {
|
||||
$tax_rate_level = sprintf('%0.2f', $item->tax_rate1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//totals
|
||||
if($this->invoice->tax_rate1 > $tax_rate_level) {
|
||||
$tax_rate_level = sprintf('%0.2f', $this->invoice->tax_rate1);
|
||||
}
|
||||
|
||||
$subtotal = sprintf('%0.2f', $calc->getSubTotal());
|
||||
$taxes = sprintf('%0.2f', $calc->getTotalTaxes());
|
||||
|
||||
$datiRiepilogo = new DatiRiepilogo;
|
||||
$datiRiepilogo->AliquotaIVA = "{$tax_rate_level}";
|
||||
$datiRiepilogo->ImponibileImporto = "{$subtotal}";
|
||||
$datiRiepilogo->Imposta = "{$taxes}";
|
||||
$datiRiepilogo->EsigibilitaIVA = "I";
|
||||
|
||||
$datiBeniServizi->DatiRiepilogo[] = $datiRiepilogo;
|
||||
|
||||
$this->FatturaElettronicaBody->DatiBeniServizi = $datiBeniServizi;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setFatturaElettronica(): self
|
||||
{
|
||||
|
||||
$this->FatturaElettronica->FatturaElettronicaBody[] = $this->FatturaElettronicaBody;
|
||||
$this->FatturaElettronica->FatturaElettronicaHeader = $this->FatturaElettronicaHeader;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function init(): self
|
||||
{
|
||||
|
||||
$this->FatturaElettronica = new FatturaElettronica;
|
||||
$this->FatturaElettronicaBody = new FatturaElettronicaBody;
|
||||
$this->FatturaElettronicaHeader = new FatturaElettronicaHeader;
|
||||
$this->DatiTrasmissione = new DatiTrasmissione;
|
||||
$this->IdTrasmittente = new IdTrasmittente;
|
||||
$this->CedentePrestatore = new CedentePrestatore;
|
||||
$this->DatiAnagrafici = new DatiAnagrafici;
|
||||
$this->IdFiscaleIVA = new IdFiscaleIVA;
|
||||
$this->Anagrafica = new Anagrafica;
|
||||
$this->DatiGeneraliDocumento = new DatiGeneraliDocumento;
|
||||
$this->DatiGenerali = new DatiGenerali;
|
||||
$this->DettaglioPagamento = new DettaglioPagamento;
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
}
|
@ -302,7 +302,26 @@ class Email implements ShouldQueue
|
||||
$this->cleanUpMailers();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
return;
|
||||
} catch (\Exception | \RuntimeException | \Google\Service\Exception $e) {
|
||||
}
|
||||
catch(\Symfony\Component\Mailer\Transport\Dsn $e){
|
||||
nlog("Incorrectly configured mail server - setting to default mail driver.");
|
||||
$this->email_object->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
}
|
||||
catch(\Google\Service\Exception $e){
|
||||
|
||||
if ($e->getCode() == '429') {
|
||||
|
||||
$message = "Google rate limiting triggered, we are queueing based on Gmail requirements.";
|
||||
$this->logMailError($message, $this->company->clients()->first());
|
||||
sleep(rand(1, 2));
|
||||
$this->release(900);
|
||||
$message = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
catch (\Exception | \RuntimeException $e) {
|
||||
nlog("Mailer failed with {$e->getMessage()}");
|
||||
$message = $e->getMessage();
|
||||
|
||||
@ -916,15 +935,20 @@ class Email implements ShouldQueue
|
||||
$guzzle = new \GuzzleHttp\Client();
|
||||
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
|
||||
|
||||
$token = json_decode($guzzle->post($url, [
|
||||
'form_params' => [
|
||||
'client_id' => config('ninja.o365.client_id'),
|
||||
'client_secret' => config('ninja.o365.client_secret'),
|
||||
'scope' => 'email Mail.Send offline_access profile User.Read openid',
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $user->oauth_user_refresh_token
|
||||
],
|
||||
])->getBody()->getContents());
|
||||
try {
|
||||
$token = json_decode($guzzle->post($url, [
|
||||
'form_params' => [
|
||||
'client_id' => config('ninja.o365.client_id'),
|
||||
'client_secret' => config('ninja.o365.client_secret'),
|
||||
'scope' => 'email Mail.Send offline_access profile User.Read openid',
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $user->oauth_user_refresh_token
|
||||
],
|
||||
])->getBody()->getContents());
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("Problem getting new Microsoft token for User: {$user->email}");
|
||||
}
|
||||
|
||||
if ($token) {
|
||||
$user->oauth_user_refresh_token = property_exists($token, 'refresh_token') ? $token->refresh_token : $user->oauth_user_refresh_token;
|
||||
|
@ -166,9 +166,9 @@ class EmailDefaults
|
||||
private function setBody(): self
|
||||
{
|
||||
|
||||
if (strlen($this->email->email_object->body) > 3) {
|
||||
if (strlen($this->email->email_object->body ?? '') > 3) {
|
||||
// A Custom Message has been set in the email screen.
|
||||
} elseif (strlen($this->email->email_object->settings?->{$this->email->email_object->email_template_body}) > 3) {
|
||||
} elseif (strlen($this->email->email_object->settings?->{$this->email->email_object->email_template_body} ?? '') > 3) {
|
||||
// A body has been saved in the settings.
|
||||
$this->email->email_object->body = $this->email->email_object->settings?->{$this->email->email_object->email_template_body};
|
||||
} else {
|
||||
|
@ -59,6 +59,7 @@ class DeletePayment
|
||||
$this->payment->delete();
|
||||
|
||||
BankTransaction::query()->where('payment_id', $this->payment->id)->cursor()->each(function ($bt) {
|
||||
$bt->invoice_ids = null;
|
||||
$bt->payment_id = null;
|
||||
$bt->status_id = 1;
|
||||
$bt->save();
|
||||
|
@ -27,11 +27,13 @@ use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\Vendor;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PdfMock
|
||||
{
|
||||
use MakesHash;
|
||||
use GeneratesCounter;
|
||||
|
||||
private mixed $mock;
|
||||
|
||||
@ -206,6 +208,20 @@ class PdfMock
|
||||
*/
|
||||
public function getStubVariables(): array
|
||||
{
|
||||
$entity_pattern = $this->entity_string.'_number_pattern';
|
||||
$entity_number = '0029';
|
||||
|
||||
if (!empty($this->settings->{$entity_pattern})) {
|
||||
// Although $this->mock is the Invoice/etc entity,
|
||||
// we need the invitation to get company details.
|
||||
$entity_number = $this->getFormattedEntityNumber(
|
||||
$this->mock->invitation,
|
||||
(int) $entity_number,
|
||||
$this->settings->counter_padding,
|
||||
$this->settings->{$entity_pattern},
|
||||
);
|
||||
}
|
||||
|
||||
return ['values' =>
|
||||
[
|
||||
'$client.shipping_postal_code' => '46420',
|
||||
@ -309,10 +325,10 @@ class PdfMock
|
||||
'$invoice.custom2' => 'custom value',
|
||||
'$invoice.custom3' => 'custom value',
|
||||
'$invoice.custom4' => 'custom value',
|
||||
'$company.custom1' => $this->company->custom_value1,
|
||||
'$company.custom2' => $this->company->custom_value2,
|
||||
'$company.custom3' => $this->company->custom_value3,
|
||||
'$company.custom4' => $this->company->custom_value4,
|
||||
'$company.custom1' => $this->company->settings->custom_value1,
|
||||
'$company.custom2' => $this->company->settings->custom_value2,
|
||||
'$company.custom3' => $this->company->settings->custom_value3,
|
||||
'$company.custom4' => $this->company->settings->custom_value4,
|
||||
'$quote.po_number' => 'PO12345',
|
||||
'$company.website' => $this->settings->website,
|
||||
'$balance_due_raw' => '0.00',
|
||||
@ -325,7 +341,7 @@ class PdfMock
|
||||
'$client.currency' => 'USD',
|
||||
'$company.country' => $this->company->country()?->name ?? 'USA',
|
||||
'$company.address' => $this->company->present()->address(),
|
||||
'$tech_hero_image' => 'http://ninja.test:8000/images/pdf-designs/tech-hero-image.jpg',
|
||||
'$tech_hero_image' => 'https://invoicing.co/images/pdf-designs/tech-hero-image.jpg',
|
||||
'$task.tax_name1' => '',
|
||||
'$task.tax_name2' => '',
|
||||
'$task.tax_name3' => '',
|
||||
@ -370,7 +386,7 @@ class PdfMock
|
||||
'$company.phone' => $this->settings->phone,
|
||||
'$company.state' => $this->settings->state,
|
||||
'$credit.number' => '0029',
|
||||
'$entity_number' => '0029',
|
||||
'$entity_number' => $entity_number,
|
||||
'$credit_number' => '0029',
|
||||
'$global_margin' => '6.35mm',
|
||||
'$contact.phone' => '681-480-9828',
|
||||
@ -456,10 +472,10 @@ class PdfMock
|
||||
'$task.tax' => '',
|
||||
'$discount' => '$0.00',
|
||||
'$subtotal' => '$0.00',
|
||||
'$company1' => $this->company->custom_value1,
|
||||
'$company2' => $this->company->custom_value2,
|
||||
'$company3' => $this->company->custom_value3,
|
||||
'$company4' => $this->company->custom_value4,
|
||||
'$company1' => $this->company->settings->custom_value1,
|
||||
'$company2' => $this->company->settings->custom_value2,
|
||||
'$company3' => $this->company->settings->custom_value3,
|
||||
'$company4' => $this->company->settings->custom_value4,
|
||||
'$due_date' => '2022-01-01',
|
||||
'$poNumber' => 'PO-123456',
|
||||
'$quote_no' => '0029',
|
||||
@ -606,10 +622,10 @@ class PdfMock
|
||||
'$task.description_label' => ctrans('texts.description'),
|
||||
'$product.discount_label' => ctrans('texts.discount'),
|
||||
'$product.quantity_label' => ctrans('texts.quantity'),
|
||||
'$entity_issued_to_label' => ctrans('texts.quote_issued_to'),
|
||||
'$entity_issued_to_label' => ctrans("texts.{$this->entity_string}_issued_to") ?: ctrans('texts.quote_issued_to'),
|
||||
'$partial_due_date_label' => ctrans('texts.partial_due_date'),
|
||||
'$invoice.datetime_label' => ctrans('texts.datetime_format_id'),
|
||||
'$invoice.due_date_label' => ctrans('texts.due_date'),
|
||||
'$invoice.due_date_label' => ctrans('texts.invoice_due_date'),
|
||||
'$company.address1_label' => ctrans('texts.address1'),
|
||||
'$company.address2_label' => ctrans('texts.address2'),
|
||||
'$total_tax_labels_label' => ctrans('texts.total_taxes'),
|
||||
@ -704,16 +720,16 @@ class PdfMock
|
||||
'$contact.email_label' => ctrans('texts.email'),
|
||||
'$invoice.taxes_label' => ctrans('texts.taxes'),
|
||||
'$credit_amount_label' => ctrans('texts.credit_amount'),
|
||||
'$invoice.total_label' => ctrans('texts.total'),
|
||||
'$invoice.total_label' => ctrans('texts.invoice_total'),
|
||||
'$product.date_label' => ctrans('texts.date'),
|
||||
'$product.item_label' => ctrans('texts.item'),
|
||||
'$public_notes_label' => ctrans('texts.public_notes'),
|
||||
'$entity.terms_label' => ctrans('texts.terms'),
|
||||
'$task.service_label' => ctrans('texts.service'),
|
||||
'$portalButton_label' => '',
|
||||
'$payment.date_label' => ctrans('texts.date'),
|
||||
'$payment.date_label' => ctrans('texts.payment_date'),
|
||||
'$client.phone_label' => ctrans('texts.phone'),
|
||||
'$invoice.date_label' => ctrans('texts.date'),
|
||||
'$invoice.date_label' => ctrans('texts.invoice_date'),
|
||||
'$client.state_label' => ctrans('texts.state'),
|
||||
'$number_short_label' => '',
|
||||
'$quote.number_label' => ctrans('texts.number'),
|
||||
|
@ -288,7 +288,6 @@ trait GeneratesCounter
|
||||
*/
|
||||
public function getNextProjectNumber(Project $project): string
|
||||
{
|
||||
|
||||
$entity_number = $this->getNextEntityNumber(Project::class, $project->client, false);
|
||||
|
||||
return $this->replaceUserVars($project, $entity_number);
|
||||
@ -412,7 +411,7 @@ trait GeneratesCounter
|
||||
*
|
||||
* @param string $pattern
|
||||
* @param string $prefix
|
||||
* @return string The padded and prefixed entity number
|
||||
* @return string The padded, prefixed and unique entity number
|
||||
*/
|
||||
private function checkEntityNumber($class, $entity, $counter, $padding, $pattern, $prefix = ''): string
|
||||
{
|
||||
@ -420,11 +419,7 @@ trait GeneratesCounter
|
||||
$check_counter = 1;
|
||||
|
||||
do {
|
||||
$number = $this->padCounter($counter, $padding);
|
||||
|
||||
$number = $this->applyNumberPattern($entity, $number, $pattern);
|
||||
|
||||
$number = $this->prefixCounter($number, $prefix);
|
||||
$number = $this->getFormattedEntityNumber($entity, $counter, $padding, $pattern);
|
||||
|
||||
$check = $class::where('company_id', $entity->company_id)->where('number', $number)->withTrashed()->exists();
|
||||
|
||||
@ -443,6 +438,26 @@ trait GeneratesCounter
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the entity number according to pattern, prefix and padding.
|
||||
*
|
||||
* @param Collection $entity The entity ie App\Models\Client, Invoice, Quote etc
|
||||
* @param int $counter The counter
|
||||
* @param int $padding The padding
|
||||
* @param string $pattern
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return string The padded and prefixed entity number
|
||||
*/
|
||||
public function getFormattedEntityNumber($entity, $counter, $padding, $pattern, $prefix = ''): string
|
||||
{
|
||||
$number = $this->padCounter($counter, $padding);
|
||||
|
||||
$number = $this->applyNumberPattern($entity, $number, $pattern);
|
||||
|
||||
return $this->prefixCounter($number, $prefix);
|
||||
}
|
||||
|
||||
/*Check if a number is available for use. */
|
||||
public function checkNumberAvailable($class, $entity, $number): bool
|
||||
{
|
||||
|
@ -31,7 +31,7 @@
|
||||
],
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^8.1|^8.2",
|
||||
"php": "^8.2",
|
||||
"ext-dom": "*",
|
||||
"ext-json": "*",
|
||||
"ext-libxml": "*",
|
||||
@ -56,7 +56,6 @@
|
||||
"hashids/hashids": "^4.0",
|
||||
"hedii/laravel-gelf-logger": "^8",
|
||||
"horstoeko/zugferd": "^1",
|
||||
"horstoeko/orderx": "^1",
|
||||
"imdhemy/laravel-purchases": "^1.7",
|
||||
"intervention/image": "^2.5",
|
||||
"invoiceninja/inspector": "^2.0",
|
||||
@ -87,7 +86,6 @@
|
||||
"sentry/sentry-laravel": "^3",
|
||||
"setasign/fpdf": "^1.8",
|
||||
"setasign/fpdi": "^2.3",
|
||||
"shopify/shopify-api": "^4.3",
|
||||
"socialiteproviders/apple": "dev-master",
|
||||
"socialiteproviders/microsoft": "^4.1",
|
||||
"spatie/laravel-data": "^3.5",
|
||||
@ -103,12 +101,13 @@
|
||||
"twig/twig": "^3",
|
||||
"twilio/sdk": "^6.40",
|
||||
"webpatser/laravel-countries": "dev-master#75992ad",
|
||||
"wepay/php-sdk": "^0.3",
|
||||
"wildbit/postmark-php": "^4.0",
|
||||
"hyvor/php-json-exporter": "^0.0.3"
|
||||
"hyvor/php-json-exporter": "^0.0.3",
|
||||
"invoiceninja/einvoice": "dev-main",
|
||||
"horstoeko/orderx": "dev-master"
|
||||
},
|
||||
"require-dev": {
|
||||
"php": "^8.1|^8.2",
|
||||
"php": "^8.2",
|
||||
"barryvdh/laravel-debugbar": "^3.6",
|
||||
"barryvdh/laravel-ide-helper": "^2.13",
|
||||
"beyondcode/laravel-query-detector": "^1.8",
|
||||
@ -120,7 +119,7 @@
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"phpunit/phpunit": "^10",
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"spaze/phpstan-stripe": "^3.0"
|
||||
},
|
||||
@ -184,6 +183,10 @@
|
||||
{
|
||||
"type":"vcs",
|
||||
"url": "https://github.com/invoiceninja/einvoice"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/turbo124/orderx"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
|
1037
composer.lock
generated
1037
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -54,17 +54,14 @@ return [
|
||||
'hosted' => env('TERMS_OF_SERVICE_URL', 'https://www.invoiceninja.com/terms/'),
|
||||
'selfhost' => env('TERMS_OF_SERVICE_URL', 'https://www.invoiceninja.com/self-hosting-terms-service/'),
|
||||
],
|
||||
|
||||
'privacy_policy_url' => [
|
||||
'hosted' => env('PRIVACY_POLICY_URL', 'https://www.invoiceninja.com/privacy-policy/'),
|
||||
'selfhost' => env('PRIVACY_POLICY_URL', 'https://www.invoiceninja.com/self-hosting-privacy-data-control/'),
|
||||
],
|
||||
|
||||
'db' => [
|
||||
'multi_db_enabled' => env('MULTI_DB_ENABLED', false),
|
||||
'default' => env('DB_CONNECTION', 'mysql'),
|
||||
],
|
||||
|
||||
'i18n' => [
|
||||
'timezone_id' => env('DEFAULT_TIMEZONE', 1),
|
||||
'country_id' => env('DEFAULT_COUNTRY', 840), // United Stated
|
||||
@ -79,7 +76,6 @@ return [
|
||||
'first_day_of_week' => env('FIRST_DATE_OF_WEEK', 0),
|
||||
'first_month_of_year' => env('FIRST_MONTH_OF_YEAR', '2000-01-01'),
|
||||
],
|
||||
|
||||
'testvars' => [
|
||||
'username' => 'user@example.com',
|
||||
'clientname' => 'client@example.com',
|
||||
|
@ -13,6 +13,7 @@ namespace Database\Factories;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Str;
|
||||
|
||||
class ClientFactory extends Factory
|
||||
{
|
||||
@ -49,6 +50,7 @@ class ClientFactory extends Factory
|
||||
'shipping_country_id' => 4,
|
||||
'settings' => ClientSettings::defaults(),
|
||||
'client_hash' => \Illuminate\Support\Str::random(40),
|
||||
'routing_id' => rand(100000,200000),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Currency;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if($currency = Currency::find(89)){
|
||||
$currency->precision = 3;
|
||||
$currency->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -111,7 +111,7 @@ class CurrenciesSeeder extends Seeder
|
||||
['id' => 86, 'name' => 'CFP Franc', 'code' => 'XPF', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], // precision should be zero
|
||||
['id' => 87, 'name' => 'Mauritian Rupee', 'code' => 'MUR', 'symbol' => 'Rs', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 88, 'name' => 'Cape Verdean Escudo', 'code' => 'CVE', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => '$'],
|
||||
['id' => 89, 'name' => 'Kuwaiti Dinar', 'code' => 'KWD', 'symbol' => 'KD', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 89, 'name' => 'Kuwaiti Dinar', 'code' => 'KWD', 'symbol' => 'KD', 'precision' => '3', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 90, 'name' => 'Algerian Dinar', 'code' => 'DZD', 'symbol' => 'DA', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 91, 'name' => 'Macedonian Denar', 'code' => 'MKD', 'symbol' => 'ден', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 92, 'name' => 'Fijian Dollar', 'code' => 'FJD', 'symbol' => 'FJ$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
|
@ -199,7 +199,7 @@ $lang = array(
|
||||
'removed_logo' => 'Successfully removed logo',
|
||||
'sent_message' => 'Successfully sent message',
|
||||
'invoice_error' => 'Please make sure to select a client and correct any errors',
|
||||
'limit_clients' => 'You\'ve hit the :count client limit on Free accounts. Congrats on your success!.',
|
||||
'limit_clients' => 'You\'ve hit the :count client limit on Free accounts. Congrats on your success!',
|
||||
'payment_error' => 'There was an error processing your payment. Please try again later.',
|
||||
'registration_required' => 'Registration Required',
|
||||
'confirmation_required' => 'Please confirm your email address, :link to resend the confirmation email.',
|
||||
@ -5147,7 +5147,7 @@ $lang = array(
|
||||
'payment_refund_receipt' => 'Payment Refund Receipt # :number',
|
||||
'payment_receipt' => 'Payment Receipt # :number',
|
||||
'load_template_description' => 'The template will be applied to following:',
|
||||
'run_template' => 'Run template',
|
||||
'run_template' => 'Run Template',
|
||||
'statement_design' => 'Statement Design',
|
||||
'delivery_note_design' => 'Delivery Note Design',
|
||||
'payment_receipt_design' => 'Payment Receipt Design',
|
||||
@ -5306,18 +5306,20 @@ $lang = array(
|
||||
'activity_140' => 'Statement sent to :client',
|
||||
'invoice_net_amount' => 'Invoice Net Amount',
|
||||
'round_to_minutes' => 'Round To Minutes',
|
||||
'1_second' => '1 Second',
|
||||
'1_minute' => '1 Minute',
|
||||
'5_minutes' => '5 Minutes',
|
||||
'15_minutes' => '15 Minutes',
|
||||
'30_minutes' => '30 Minutes',
|
||||
'1_hour' => '1 Hour',
|
||||
'1_day' => '1 Day',
|
||||
'round_tasks' => 'Round Tasks',
|
||||
'round_tasks_help' => 'Round time intervals when saving tasks',
|
||||
'round_tasks' => 'Task Rounding Direction',
|
||||
'round_tasks_help' => 'Round task times up or down.',
|
||||
'direction' => 'Direction',
|
||||
'round_up' => 'Round Up',
|
||||
'round_down' => 'Round Down',
|
||||
'task_round_to_nearest' => 'Round To Nearest',
|
||||
'task_round_to_nearest_help' => 'The interval to round the task to.',
|
||||
'bulk_updated' => 'Successfully updated data',
|
||||
'bulk_update' => 'Bulk Update',
|
||||
'calculate' => 'Calculate',
|
||||
@ -5330,6 +5332,8 @@ $lang = array(
|
||||
'e_invoice_settings' => 'E-Invoice Settings',
|
||||
'btcpay_refund_subject' => 'Refund of your invoice via BTCPay',
|
||||
'btcpay_refund_body' => 'A refund intended for you has been issued. To claim it via BTCPay, please click on this link:',
|
||||
'currency_mauritanian_ouguiya' => 'Mauritanian Ouguiya',
|
||||
'currency_bhutan_ngultrum' => 'Bhutan Ngultrum',
|
||||
);
|
||||
|
||||
return $lang;
|
@ -5322,6 +5322,12 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
|
||||
'money' => 'Argent',
|
||||
'web_app' => 'App web',
|
||||
'desktop_app' => 'App de bureau',
|
||||
'disconnected' => 'Déconnecté',
|
||||
'reconnect' => 'Reconnecté',
|
||||
'e_invoice_settings' => 'Paramètres E-Facture',
|
||||
'currency_mauritanian_ouguiya' => 'Ouguiya mauritanien',
|
||||
'currency_bhutan_ngultrum' => 'Ngultrum Bhoutan',
|
||||
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
1982
package-lock.json
generated
1982
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@ -9,18 +9,20 @@
|
||||
"@babel/compat-data": "7.15.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"cypress": "^12.5.1",
|
||||
"laravel-mix-purgecss": "^6.0.0",
|
||||
"laravel-vite-plugin": "^0.8.0",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-static-copy": "^0.17.0",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/forms": "^0.3.4",
|
||||
"@tailwindcss/line-clamp": "^0.3.1",
|
||||
"@tailwindcss/typography": "^0.4.1",
|
||||
"autoprefixer": "^10.3.7",
|
||||
"axios": "^0.25",
|
||||
"card-js": "^1.0.13",
|
||||
"card-validator": "^8.1.1",
|
||||
@ -31,11 +33,9 @@
|
||||
"laravel-mix": "^6.0.34",
|
||||
"linkify-urls": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"postcss": "^8.3.11",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
"sass": "^1.43.4",
|
||||
"sass-loader": "^12.3.0",
|
||||
"tailwindcss": "^2.2.17"
|
||||
"sass-loader": "^12.3.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="DB_CONNECTION" value="sqlite"/>
|
||||
<env name="DB_DATABASE" value=":memory:"/>
|
||||
<env name="DB_STRICT" value="FALSE"/>
|
||||
<env name="DB_STRICT" value="false"/>
|
||||
</php>
|
||||
<logging/>
|
||||
</phpunit>
|
||||
|
109
public/build/assets/app-8722f22d.js
vendored
Normal file
109
public/build/assets/app-8722f22d.js
vendored
Normal file
File diff suppressed because one or more lines are too long
109
public/build/assets/app-a52d5f77.js
vendored
109
public/build/assets/app-a52d5f77.js
vendored
File diff suppressed because one or more lines are too long
1
public/build/assets/app-c6dc74fe.css
vendored
1
public/build/assets/app-c6dc74fe.css
vendored
File diff suppressed because one or more lines are too long
1
public/build/assets/app-c7c5fad4.css
vendored
Normal file
1
public/build/assets/app-c7c5fad4.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"resources/js/app.js": {
|
||||
"file": "assets/app-a52d5f77.js",
|
||||
"file": "assets/app-8722f22d.js",
|
||||
"imports": [
|
||||
"_index-08e160a7.js",
|
||||
"__commonjsHelpers-725317a4.js"
|
||||
@ -240,7 +240,7 @@
|
||||
"src": "resources/js/setup/setup.js"
|
||||
},
|
||||
"resources/sass/app.scss": {
|
||||
"file": "assets/app-c6dc74fe.css",
|
||||
"file": "assets/app-c7c5fad4.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/sass/app.scss"
|
||||
}
|
||||
|
2
resources/sass/components/alerts.scss
vendored
2
resources/sass/components/alerts.scss
vendored
@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
@apply border-green-500;
|
||||
@apply border-emerald-500;
|
||||
}
|
||||
|
||||
.alert-failure {
|
||||
|
4
resources/sass/components/badge.scss
vendored
4
resources/sass/components/badge.scss
vendored
@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
@apply bg-green-100 text-green-500;
|
||||
@apply bg-emerald-100 text-emerald-500;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
@ -23,7 +23,7 @@
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
@apply bg-blue-500 text-yellow-600;
|
||||
@apply bg-blue-500 text-amber-600;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
|
2
resources/sass/components/validation.scss
vendored
2
resources/sass/components/validation.scss
vendored
@ -7,5 +7,5 @@
|
||||
}
|
||||
|
||||
.validation-pass {
|
||||
@apply border-green-500 text-gray-700 text-sm;
|
||||
@apply border-emerald-500 text-gray-700 text-sm;
|
||||
}
|
||||
|
@ -132,7 +132,7 @@
|
||||
|
||||
<div class="flex justify-between items-center mt-8">
|
||||
|
||||
<a href="{{route('client.login')}}" class="button button-info bg-green-600 text-white">{{ ctrans('texts.login_label') }}</a>
|
||||
<a href="{{route('client.login')}}" class="button button-info bg-emerald-600 text-white">{{ ctrans('texts.login_label') }}</a>
|
||||
|
||||
<span class="inline-flex items-center" x-data="{ terms_of_service: false, privacy_policy: false }">
|
||||
@if(!empty($register_company->settings->client_portal_terms) || !empty($register_company->settings->client_portal_privacy_policy))
|
||||
|
@ -250,7 +250,7 @@
|
||||
|
||||
@if($steps['passwordless_login_sent'])
|
||||
<span
|
||||
class="block mt-2 text-sm text-green-600">{!! ctrans('texts.sent') !!}</span>
|
||||
class="block mt-2 text-sm text-emerald-600">{!! ctrans('texts.sent') !!}</span>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
|
@ -5,10 +5,10 @@
|
||||
<div class="flex flex-col xl:flex-row gap-4">
|
||||
<div class="w-full rounded-md border border-[#E5E7EB] bg-white p-5 text-sm text-[#6C727F]">
|
||||
<h3 class="mb-4 text-xl font-semibold text-[#212529]">{{ $contact->first_name }} {{ $contact->last_name }}</h3>
|
||||
<p class="mb-1.5">{{ $contact->phone }}</p>
|
||||
<p class="mb-4">{{ $client->address1 }}</p>
|
||||
<p class="mb-1.5">{{ $client->city }}, {{ $client->state }}</p>
|
||||
<p class="mb-1.5">{{ $client->postal_code }}</p>
|
||||
<p>{{ $contact->phone }}</p>
|
||||
<p>{{ $client->address1 }}</p>
|
||||
<p>{{ $client->city }}, {{ $client->state }}</p>
|
||||
<p>{{ $client->postal_code }}</p>
|
||||
<p>{{ App\Models\Country::find($client->country_id)?->name }}</p>
|
||||
</div>
|
||||
|
||||
@ -81,16 +81,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-light-grey-text flex grow basis-full flex-col justify-center pt-5 text-sm md:basis-1/2 md:border-r md:border-[#E5E7EB] md:pt-0 xl:basis-auto xl:px-5">
|
||||
<p class="mb-2">{{ $client->company->settings->address1 }}</p>
|
||||
<p class="mb-2">{{ $client->company->settings->address2 }}</p>
|
||||
<p class="mb-2">{{ $client->company->settings->postal_code }}</p>
|
||||
<div class="text-light-grey-text flex grow basis-full flex-col justify-center pt-5 text-sm md:basis-1/2 md:border-r md:border-[#E5E7EB] md:pt-0 xl:basis-auto xl:px-5 space-y-2">
|
||||
<p>{{ $client->company->settings->address1 }}</p>
|
||||
<p>{{ $client->company->settings->city }} {{ $client->company->settings->state }}</p>
|
||||
<p>{{ $client->company->settings->postal_code }}</p>
|
||||
<p>{{ App\Models\Country::find($client->company->settings->country_id)?->name }}</p>
|
||||
</div>
|
||||
<div class="text-light-grey-text flex grow basis-full flex-col justify-center text-sm md:basis-1/2 md:pl-4 xl:basis-auto xl:px-5">
|
||||
<p class="mb-2">{{ $client->company->settings->email }}</p>
|
||||
<p class="mb-2">{{ $client->company->settings->phone }}</p>
|
||||
<p>{{ $client->company->settings->website }}</p>
|
||||
|
||||
<div class="text-light-grey-text flex grow basis-full flex-col justify-center text-sm md:basis-1/2 md:pl-4 xl:basis-auto xl:px-5 space-y-2 mt-3 xl:mt-0">
|
||||
<p><span class="font-semibold">{{ ctrans('texts.vat') }}</span>: {{ $client->company->settings->vat_number }}</p>
|
||||
<p>
|
||||
<a class="underline" href="mailto:{{ $client->company->settings->email }}" target="_blank">{{ $client->company->settings->email }}</a>
|
||||
</p>
|
||||
<p>{{ $client->company->settings->phone }}</p>
|
||||
<p>
|
||||
<a class="underline" href="{{ $client->company->settings->website }}" target="_blank">
|
||||
{{ $client->company->settings->website }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
||||
|
@ -34,7 +34,7 @@
|
||||
|
||||
@if(Request::isSecure())
|
||||
<span class="block mx-4 mb-4 text-xs inline-flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-600"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-emerald-600"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
|
||||
<span class="ml-1">Secure 256-bit encryption</span>
|
||||
</span>
|
||||
@endif
|
||||
|
@ -56,7 +56,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white shadow rounded-sm mb-4 mt-4 border-l-2 border-green-500" translate>
|
||||
<div class="bg-white shadow rounded-sm mb-4 mt-4 border-l-2 border-emerald-500" translate>
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
|
@ -13,7 +13,6 @@ use App\Http\Controllers\Gateways\Mollie3dsController;
|
||||
use App\Http\Controllers\SetupController;
|
||||
use App\Http\Controllers\StripeConnectController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\WePayController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', [BaseController::class, 'flutterRoute'])->middleware('guest');
|
||||
@ -31,9 +30,6 @@ Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEm
|
||||
Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])->middleware(['domain_db', 'email_db'])->name('password.reset');
|
||||
Route::post('password/reset', [ResetPasswordController::class, 'reset'])->middleware('email_db')->name('password.update');
|
||||
|
||||
Route::get('wepay/signup/{token}', [WePayController::class, 'signup'])->name('wepay.signup');
|
||||
Route::get('wepay/finished', [WePayController::class, 'finished'])->name('wepay.finished');
|
||||
|
||||
Route::get('auth/{provider}', [LoginController::class, 'redirectToProvider']);
|
||||
|
||||
Route::middleware('url_db')->group(function () {
|
||||
|
@ -1,27 +1,24 @@
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
const defaultTheme = require('tailwindcss/defaultTheme');
|
||||
|
||||
module.exports = {
|
||||
purge: [
|
||||
content: [
|
||||
'./resources/views/portal/ninja2020/**/*.blade.php',
|
||||
'./resources/views/email/template/**/*.blade.php',
|
||||
'./resources/views/email/components/**/*.blade.php',
|
||||
'./resources/views/themes/ninja2020/**/*.blade.php',
|
||||
'./resources/views/auth/**/*.blade.php',
|
||||
'./resources/views/setup/**/*.blade.php',
|
||||
'./resources/views/billing-portal/**/*.blade.php',
|
||||
'./resources/views/billing-portal/**/*.blade.php'
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ["Open Sans", ...defaultTheme.fontFamily.sans]
|
||||
}
|
||||
}
|
||||
sans: ['Open Sans', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [
|
||||
require('@tailwindcss/line-clamp'),
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/typography'),
|
||||
]
|
||||
|
||||
],
|
||||
};
|
||||
|
@ -11,13 +11,14 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Tests\TestCase;
|
||||
use Tests\MockAccountData;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
@ -29,6 +30,9 @@ class ClientGatewayTokenApiTest extends TestCase
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
|
||||
protected $faker;
|
||||
protected CompanyGateway $cg;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
@ -11,11 +11,21 @@
|
||||
|
||||
namespace Tests\Feature\EInvoice;
|
||||
|
||||
use App\Services\EDocument\Standards\FatturaPA;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use Tests\MockAccountData;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Models\Invoice;
|
||||
use Invoiceninja\Einvoice\Symfony\Encode;
|
||||
use App\Services\EDocument\Standards\FatturaPANew;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronica;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronicaBodyType\FatturaElettronicaBody;
|
||||
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronicaHeaderType\FatturaElettronicaHeader;
|
||||
|
||||
/**
|
||||
* @test
|
||||
@ -31,6 +41,9 @@ class FatturaPATest extends TestCase
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
|
||||
$this->markTestSkipped('prevent running in CI');
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
@ -38,9 +51,86 @@ class FatturaPATest extends TestCase
|
||||
|
||||
public function testInvoiceBoot()
|
||||
{
|
||||
$fat = new FatturaPA($this->invoice);
|
||||
$xml = $fat->run();
|
||||
|
||||
$this->assertnotNull($xml);
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->address1 = 'Via Silvio Spaventa 108';
|
||||
$settings->city = 'Calcinelli';
|
||||
|
||||
$settings->state = 'PA';
|
||||
|
||||
// $settings->state = 'Perugia';
|
||||
$settings->postal_code = '61030';
|
||||
$settings->country_id = '380';
|
||||
$settings->currency_id = '3';
|
||||
$settings->vat_number = '01234567890';
|
||||
$settings->id_number = '';
|
||||
|
||||
$company = Company::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'settings' => $settings,
|
||||
]);
|
||||
|
||||
$client_settings = ClientSettings::defaults();
|
||||
$client_settings->currency_id = '3';
|
||||
|
||||
$client = Client::factory()->create([
|
||||
'company_id' => $company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'name' => 'Italian Client Name',
|
||||
'address1' => 'Via Antonio da Legnago 68',
|
||||
'city' => 'Monasterace',
|
||||
'state' => 'CR',
|
||||
// 'state' => 'Reggio Calabria',
|
||||
'postal_code' => '89040',
|
||||
'country_id' => 380,
|
||||
'routing_id' => 'ABC1234',
|
||||
'settings' => $client_settings,
|
||||
]);
|
||||
|
||||
$item = new InvoiceItem;
|
||||
$item->product_key = "Product Key";
|
||||
$item->notes = "Product Description";
|
||||
$item->cost = 10;
|
||||
$item->quantity = 10;
|
||||
$item->tax_rate1 = 22;
|
||||
$item->tax_name1 = 'IVA';
|
||||
|
||||
$invoice = Invoice::factory()->create([
|
||||
'company_id' => $company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $client->id,
|
||||
'discount' => 0,
|
||||
'uses_inclusive_taxes' => false,
|
||||
'status_id' => 1,
|
||||
'tax_rate1' => 0,
|
||||
'tax_name1' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_rate3' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_name3' => '',
|
||||
'line_items' => [$item],
|
||||
'number' => 'ITA-'.rand(1000,100000)
|
||||
]);
|
||||
|
||||
$invoice->service()->markSent()->save();
|
||||
|
||||
$fat = new FatturaPANew($invoice);
|
||||
$fat->run();
|
||||
|
||||
$fe = $fat->getFatturaElettronica();
|
||||
|
||||
$this->assertNotNull($fe);
|
||||
|
||||
$this->assertInstanceOf(FatturaElettronica::class, $fe);
|
||||
$this->assertInstanceOf(FatturaElettronicaBody::class, $fe->FatturaElettronicaBody[0]);
|
||||
$this->assertInstanceOf(FatturaElettronicaHeader::class, $fe->FatturaElettronicaHeader);
|
||||
|
||||
|
||||
$encoder = new Encode($fe);
|
||||
$xml = $encoder->toXml();
|
||||
|
||||
$this->assertNotNull($xml);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
452
tests/Integration/Einvoice/Fact1Test.php
Normal file
452
tests/Integration/Einvoice/Fact1Test.php
Normal file
@ -0,0 +1,452 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Integration\Einvoice;
|
||||
|
||||
use DateTime;
|
||||
use Tests\TestCase;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use Tests\MockAccountData;
|
||||
use App\Models\ClientContact;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\ItemType\Item;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\PartyType\Party;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\PriceType\Price;
|
||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\ContactType\Contact;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\CountryType\Country;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\AmountType\TaxAmount;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\TaxTotalType\TaxTotal;
|
||||
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\AmountType\PriceAmount;
|
||||
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\TaxSchemeType\TaxScheme;
|
||||
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\AddressType\PostalAddress;
|
||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\InvoiceLineType\InvoiceLine;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\TaxScheme as FACT1TaxScheme;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\TaxSubtotalType\TaxSubtotal;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\QuantityType\InvoicedQuantity;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\AmountType\LineExtensionAmount;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\AmountType\PayableAmount;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\AmountType\TaxableAmount;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\AmountType\TaxExclusiveAmount;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\AmountType\TaxInclusiveAmount;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\PartyTaxSchemeType\PartyTaxScheme;
|
||||
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\MonetaryTotalType\LegalMonetaryTotal;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\PartyLegalEntityType\PartyLegalEntity;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\TaxCategoryType\ClassifiedTaxCategory;
|
||||
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\CustomerPartyType\AccountingCustomerParty;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\SupplierPartyType\AccountingSupplierParty;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\PartyIdentificationType\PartyIdentification;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\TaxCategoryType\TaxCategory;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class Fact1Test extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->markTestSkipped('prevent running in CI');
|
||||
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function testRoBuild()
|
||||
{
|
||||
$settings = $this->company->settings;
|
||||
$settings->currency_id = '42';
|
||||
$this->company->saveSettings($settings, $this->company);
|
||||
$this->company->save();
|
||||
|
||||
$settings = ClientSettings::defaults();
|
||||
$settings->currency_id = '42';
|
||||
|
||||
//VAT
|
||||
//19%
|
||||
$client = Client::factory()
|
||||
->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
'id_number' => '646546549',
|
||||
'address1' => '40D, Șoseaua București-Ploiești',
|
||||
'city' => 'SECTOR3',
|
||||
'state' => 'RO-B',
|
||||
'country_id' => 642,
|
||||
'vat_number' => 646546549,
|
||||
'name' => 'Client Company Name',
|
||||
'settings' => $settings,
|
||||
]);
|
||||
|
||||
ClientContact::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
'client_id' => $client->id,
|
||||
'first_name' => 'Bob',
|
||||
'last_name' => 'Jane',
|
||||
'email' => 'bob@gmail.com',
|
||||
]);
|
||||
|
||||
$items = [];
|
||||
|
||||
$item = new InvoiceItem;
|
||||
$item->cost = 10;
|
||||
$item->quantity = 10;
|
||||
$item->tax_name1 = 'VAT';
|
||||
$item->tax_rate1 = '19';
|
||||
$item->product_key = "Product Name";
|
||||
$item->notes = "A great product description";
|
||||
|
||||
$_invoice = Invoice::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
'client_id' => $client->id,
|
||||
'number' => 'INV-'.rand(1000,1000000),
|
||||
'line_items' => [$item],
|
||||
'due_date' => now()->addDays(20)->format('Y-m-d'),
|
||||
'status_id' => 1,
|
||||
'discount' => 0,
|
||||
]);
|
||||
|
||||
$_invoice->service()->markSent()->save();
|
||||
$calc = $_invoice->calc();
|
||||
|
||||
$invoice = new \Invoiceninja\Einvoice\Models\FACT1\Invoice();
|
||||
$invoice->UBLVersionID = '2.1';
|
||||
$invoice->CustomizationID = 'urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1';
|
||||
$invoice->ID = $_invoice->number;
|
||||
$invoice->InvoiceTypeCode = 380;
|
||||
$invoice->IssueDate = new DateTime($_invoice->date);
|
||||
$invoice->DueDate = new DateTime($_invoice->due_date);
|
||||
$invoice->DocumentCurrencyCode = 'RON';
|
||||
$invoice->TaxCurrencyCode = 'RON';
|
||||
|
||||
$asp = new AccountingSupplierParty();
|
||||
$party = new Party();
|
||||
|
||||
$party_identification = new PartyIdentification();
|
||||
$party_identification->ID = 'company_id_number';
|
||||
$party->PartyIdentification[] = $party_identification;
|
||||
|
||||
$sp_address = new PostalAddress();
|
||||
$sp_address->StreetName = $this->company->settings->address1;
|
||||
$sp_address->CityName = 'SECTOR2';
|
||||
$sp_address->CountrySubentity = 'RO-B';
|
||||
|
||||
$country = new Country();
|
||||
$country->IdentificationCode='RO';
|
||||
$sp_address->Country = $country;
|
||||
|
||||
$party->PostalAddress = $sp_address;
|
||||
|
||||
$pts = new PartyTaxScheme();
|
||||
$tax_scheme = new TaxScheme();
|
||||
$tax_scheme->ID = 'VAT';
|
||||
|
||||
$pts->CompanyID = 'RO234234234';
|
||||
$pts->TaxScheme = $tax_scheme;
|
||||
|
||||
$party->PartyTaxScheme[] = $pts;
|
||||
|
||||
$ple = new PartyLegalEntity();
|
||||
$ple->RegistrationName = $this->company->settings->name;
|
||||
$ple->CompanyID = 'J40/2222/2009';
|
||||
|
||||
$party->PartyLegalEntity[] = $ple;
|
||||
|
||||
$p_contact = new Contact();
|
||||
$p_contact->Name = $this->company->owner()->present()->name();
|
||||
$p_contact->Telephone = $this->company->settings->phone;
|
||||
$p_contact->ElectronicMail = $this->company->owner()->email;
|
||||
$party->Contact = $p_contact;
|
||||
$asp->Party = $party;
|
||||
|
||||
$invoice->AccountingSupplierParty = $asp;
|
||||
|
||||
$acp = new AccountingCustomerParty();
|
||||
|
||||
$party = new Party();
|
||||
|
||||
$party_identification = new PartyIdentification();
|
||||
$party_identification->ID = 'client_id_number';
|
||||
$party->PartyIdentification[] = $party_identification;
|
||||
|
||||
$sp_address = new PostalAddress();
|
||||
$sp_address->StreetName = $client->address1;
|
||||
$sp_address->CityName = 'SECTOR2';
|
||||
$sp_address->CountrySubentity = 'RO-B';
|
||||
|
||||
$country = new Country();
|
||||
$country->IdentificationCode = 'RO';
|
||||
$sp_address->Country = $country;
|
||||
|
||||
$party->PostalAddress = $sp_address;
|
||||
|
||||
$ple = new PartyLegalEntity();
|
||||
$ple->RegistrationName = $client->name;
|
||||
$ple->CompanyID = '646546549';
|
||||
|
||||
$party->PartyLegalEntity[] = $ple;
|
||||
|
||||
$p_contact = new Contact();
|
||||
$p_contact->Name = $client->contacts->first()->present()->name();
|
||||
$p_contact->Telephone = $client->contacts->first()->present()->phone();
|
||||
$p_contact->ElectronicMail = $client->contacts->first()->present()->email();
|
||||
|
||||
$party->Contact = $p_contact;
|
||||
|
||||
$acp->Party = $party;
|
||||
$invoice->AccountingCustomerParty = $acp;
|
||||
|
||||
$taxtotal = new TaxTotal();
|
||||
$tax_amount = new TaxAmount();
|
||||
|
||||
$tax_amount->amount = $calc->getItemTotalTaxes();
|
||||
$tax_amount->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$tc = new TaxCategory();
|
||||
$tc->ID = "S";
|
||||
|
||||
$taxable = $this->getTaxable($_invoice);
|
||||
|
||||
$taxable_amount = new TaxableAmount();
|
||||
$taxable_amount->amount = $taxable;
|
||||
$taxable_amount->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$tax_sub_total = new TaxSubtotal();
|
||||
$tax_sub_total->TaxAmount = $tax_amount;
|
||||
$tax_sub_total->TaxCategory = $tc;
|
||||
$tax_sub_total->TaxableAmount = $taxable_amount;
|
||||
$taxtotal->TaxSubtotal[] = $tax_sub_total;
|
||||
|
||||
$invoice->TaxTotal[] = $taxtotal;
|
||||
|
||||
$lmt = new LegalMonetaryTotal();
|
||||
|
||||
$lea = new LineExtensionAmount();
|
||||
$lea->amount = $taxable;
|
||||
$lea->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$lmt->LineExtensionAmount = $lea;
|
||||
|
||||
$tea = new TaxExclusiveAmount;
|
||||
$tea->amount = $taxable;
|
||||
$tea->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$lmt->TaxExclusiveAmount = $tea;
|
||||
|
||||
$tia = new TaxInclusiveAmount;
|
||||
$tia->amount = $_invoice->amount;
|
||||
$tia->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$lmt->TaxInclusiveAmount = $tia;
|
||||
|
||||
$pa = new PayableAmount;
|
||||
$pa->amount = $_invoice->amount;
|
||||
$pa->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$lmt->PayableAmount = $pa;
|
||||
$invoice->LegalMonetaryTotal = $lmt;
|
||||
|
||||
foreach($_invoice->line_items as $key => $item)
|
||||
{
|
||||
|
||||
$invoice_line = new InvoiceLine;
|
||||
$invoice_line->ID = $key++;
|
||||
|
||||
$iq = new InvoicedQuantity();
|
||||
$iq->amount = $item->cost;
|
||||
$iq->unitCode = 'H87';
|
||||
|
||||
$invoice_line->InvoicedQuantity = $iq;
|
||||
|
||||
$invoice_line->Note = substr($item->notes, 0, 200);
|
||||
|
||||
$ctc = new ClassifiedTaxCategory();
|
||||
$ctc->ID = 'S';
|
||||
|
||||
$i = new Item;
|
||||
$i->Description = $item->notes;
|
||||
$i->Name = $item->product_key;
|
||||
|
||||
$tax_scheme = new FACT1TaxScheme();
|
||||
$tax_scheme->ID = $item->tax_name1;
|
||||
$tax_scheme->Name = $item->tax_rate1;
|
||||
|
||||
$ctc = new ClassifiedTaxCategory();
|
||||
$ctc->TaxScheme = $tax_scheme;
|
||||
$ctc->ID = 'S';
|
||||
|
||||
$i->ClassifiedTaxCategory[] = $ctc;
|
||||
|
||||
$invoice_line->Item = $i;
|
||||
|
||||
|
||||
$lea = new LineExtensionAmount;
|
||||
$lea->amount = $item->line_total;
|
||||
$lea->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$invoice_line->LineExtensionAmount = $lea;
|
||||
|
||||
$price = new Price();
|
||||
$pa = new PriceAmount();
|
||||
$pa->amount = $item->line_total;
|
||||
$pa->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$price->PriceAmount = $pa;
|
||||
|
||||
$lea = new LineExtensionAmount();
|
||||
$lea->amount = $item->line_total;
|
||||
$lea->currencyID = $_invoice->client->currency()->code;
|
||||
|
||||
$invoice_line->LineExtensionAmount = $lea;
|
||||
|
||||
$invoice->InvoiceLine[] = $invoice_line;
|
||||
}
|
||||
|
||||
$validator = Validation::createValidatorBuilder()
|
||||
->enableAttributeMapping()
|
||||
->getValidator();
|
||||
|
||||
$errors = $validator->validate($invoice);
|
||||
|
||||
foreach($errors as $error) {
|
||||
// echo $error->getPropertyPath() . ': ' . $error->getMessage() . "\n";
|
||||
}
|
||||
|
||||
$this->assertCount(0, $errors);
|
||||
|
||||
$phpDocExtractor = new PhpDocExtractor();
|
||||
$reflectionExtractor = new ReflectionExtractor();
|
||||
// list of PropertyListExtractorInterface (any iterable)
|
||||
$listExtractors = [$reflectionExtractor];
|
||||
// list of PropertyTypeExtractorInterface (any iterable)
|
||||
$typeExtractors = [$reflectionExtractor,$phpDocExtractor];
|
||||
// list of PropertyDescriptionExtractorInterface (any iterable)
|
||||
$descriptionExtractors = [$phpDocExtractor];
|
||||
// list of PropertyAccessExtractorInterface (any iterable)
|
||||
$accessExtractors = [$reflectionExtractor];
|
||||
// list of PropertyInitializableExtractorInterface (any iterable)
|
||||
$propertyInitializableExtractors = [$reflectionExtractor];
|
||||
$propertyInfo = new PropertyInfoExtractor(
|
||||
// $listExtractors,
|
||||
$propertyInitializableExtractors,
|
||||
$descriptionExtractors,
|
||||
$typeExtractors,
|
||||
// $accessExtractors,
|
||||
);
|
||||
$context = [
|
||||
'xml_format_output' => true,
|
||||
'remove_empty_tags' => true,
|
||||
];
|
||||
|
||||
$encoder = new XmlEncoder($context);
|
||||
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
|
||||
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
|
||||
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
|
||||
|
||||
$normalizer = new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter, null, $propertyInfo);
|
||||
|
||||
$normalizers = [ new DateTimeNormalizer(), $normalizer, new ArrayDenormalizer() , ];
|
||||
$encoders = [$encoder, new JsonEncoder()];
|
||||
$serializer = new Serializer($normalizers, $encoders);
|
||||
|
||||
$n_context = [
|
||||
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
|
||||
// AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => true,
|
||||
];
|
||||
|
||||
|
||||
// $invoice = $normalizer->normalize($invoice, 'json', $n_context);
|
||||
// echo print_r($invoice);
|
||||
// $invoice = $serializer->serialize($invoice, 'xml', $n_context);
|
||||
$dataxml = $serializer->encode($invoice, 'xml', $context);
|
||||
|
||||
// echo $dataxml;
|
||||
|
||||
//set default standard props
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return float|int|mixed
|
||||
*/
|
||||
private function getTaxable(Invoice $invoice): float
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
foreach ($invoice->line_items as $item) {
|
||||
$line_total = $item->quantity * $item->cost;
|
||||
|
||||
if ($item->discount != 0) {
|
||||
if ($invoice->is_amount_discount) {
|
||||
$line_total -= $item->discount;
|
||||
} else {
|
||||
$line_total -= $line_total * $item->discount / 100;
|
||||
}
|
||||
}
|
||||
|
||||
$total += $line_total;
|
||||
}
|
||||
|
||||
if ($invoice->discount > 0) {
|
||||
if ($invoice->is_amount_discount) {
|
||||
$total -= $invoice->discount;
|
||||
} else {
|
||||
$total *= (100 - $invoice->discount) / 100;
|
||||
$total = round($total, 2);
|
||||
}
|
||||
}
|
||||
|
||||
if ($invoice->custom_surcharge1 && $invoice->custom_surcharge_tax1) {
|
||||
$total += $invoice->custom_surcharge1;
|
||||
}
|
||||
|
||||
if ($invoice->custom_surcharge2 && $invoice->custom_surcharge_tax2) {
|
||||
$total += $invoice->custom_surcharge2;
|
||||
}
|
||||
|
||||
if ($invoice->custom_surcharge3 && $invoice->custom_surcharge_tax3) {
|
||||
$total += $invoice->custom_surcharge3;
|
||||
}
|
||||
|
||||
if ($invoice->custom_surcharge4 && $invoice->custom_surcharge_tax4) {
|
||||
$total += $invoice->custom_surcharge4;
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user