mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-08-30 23:20:34 -04:00
commit
e5ddd4e2e5
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,7 +17,6 @@ local_version.txt
|
||||
|
||||
/resources/assets/bower
|
||||
/public/logo
|
||||
/storage/*
|
||||
.env.dusk.local
|
||||
/public/vendors/*
|
||||
*.log
|
||||
@ -29,4 +28,4 @@ nbproject
|
||||
.php_cs.cache
|
||||
public/test.pdf
|
||||
public/storage/test.pdf
|
||||
/Modules
|
||||
/Modules
|
||||
|
@ -1 +1 @@
|
||||
5.3.17
|
||||
5.3.18
|
@ -56,7 +56,7 @@ class PostUpdate extends Command
|
||||
|
||||
exec('vendor/bin/composer install --no-dev -o', $output);
|
||||
|
||||
info(print_r($output,1));
|
||||
info(print_r($output,1));
|
||||
info("finished running composer install ");
|
||||
|
||||
try {
|
||||
@ -75,9 +75,17 @@ class PostUpdate extends Command
|
||||
|
||||
info("view cleared");
|
||||
|
||||
try {
|
||||
Artisan::call('queue:restart');
|
||||
} catch (\Exception $e) {
|
||||
info("I wasn't able to restart the queue.");
|
||||
}
|
||||
|
||||
info("queue restarted");
|
||||
|
||||
VersionCheck::dispatch();
|
||||
|
||||
info("Sent for version check");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
101
app/DataMapper/ClientRegistrationFields.php
Normal file
101
app/DataMapper/ClientRegistrationFields.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?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 App\DataMapper;
|
||||
|
||||
class ClientRegistrationFields
|
||||
{
|
||||
|
||||
public static function generate()
|
||||
{
|
||||
$data =
|
||||
[
|
||||
[
|
||||
'key' => 'first_name',
|
||||
'required' => true
|
||||
],
|
||||
[
|
||||
'key' => 'last_name',
|
||||
'required' => true
|
||||
],
|
||||
[
|
||||
'key' => 'email',
|
||||
'required' => true
|
||||
],
|
||||
[
|
||||
'key' => 'phone',
|
||||
'required' => true
|
||||
],
|
||||
[
|
||||
'key' => 'password',
|
||||
'required' => true
|
||||
],
|
||||
[
|
||||
'key' => 'name',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'website',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'address1',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'address2',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'city',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'state',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'postal_code',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'country_id',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'custom_value1',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'custom_value2',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'custom_value3',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'custom_value4',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'public_notes',
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'vat_number',
|
||||
'required' => false
|
||||
],
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\DataMapper\ClientRegistrationFields;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
@ -35,7 +36,8 @@ class CompanyFactory
|
||||
$company->db = config('database.default');
|
||||
//$company->custom_fields = (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3'];
|
||||
$company->custom_fields = (object) [];
|
||||
|
||||
$company->client_registration_fields = ClientRegistrationFields::generate();
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$company->subdomain = MultiDB::randomSubdomainGenerator();
|
||||
else
|
||||
|
@ -20,10 +20,10 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
class DocumentFilters extends QueryFilters
|
||||
{
|
||||
|
||||
public function client_id(int $client_id) :Builder
|
||||
{
|
||||
return $this->builder->where('client_id', $client_id);
|
||||
}
|
||||
// public function client_id(int $client_id) :Builder
|
||||
// {
|
||||
// return $this->builder->where('client_id', $client_id);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Filter based on search text.
|
||||
|
@ -746,6 +746,8 @@ class BaseController extends Controller
|
||||
//pass referral code to front end
|
||||
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
|
||||
$data['build'] = request()->has('build') ? request()->input('build') : '';
|
||||
$data['login'] = request()->has('login') ? request()->input('login') : "false";
|
||||
|
||||
$data['user_agent'] = request()->server('HTTP_USER_AGENT');
|
||||
|
||||
$data['path'] = $this->setBuild();
|
||||
@ -765,6 +767,9 @@ class BaseController extends Controller
|
||||
if(request()->has('build')) {
|
||||
$build = request()->input('build');
|
||||
}
|
||||
elseif(Ninja::isHosted()){
|
||||
return 'main.dart.js';
|
||||
}
|
||||
|
||||
switch ($build) {
|
||||
case 'wasm':
|
||||
@ -776,13 +781,10 @@ class BaseController extends Controller
|
||||
case 'next':
|
||||
return 'main.next.dart.js';
|
||||
case 'profile':
|
||||
return 'main.profile.dart.js';
|
||||
return 'main.profile.dart.js';
|
||||
default:
|
||||
return 'main.foss.dart.js';
|
||||
|
||||
if(Ninja::isSelfHost())
|
||||
return 'main.foss.dart.js';
|
||||
|
||||
return 'main.dart.js';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,7 +57,12 @@ class DocumentController extends Controller
|
||||
{
|
||||
$document = Document::where('hash', $document_hash)->firstOrFail();
|
||||
|
||||
return Storage::disk($document->disk)->download($document->url, $document->name);
|
||||
$headers = [];
|
||||
|
||||
if(request()->input('inline') == 'true')
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
|
||||
return Storage::disk($document->disk)->download($document->url, $document->name, $headers);
|
||||
}
|
||||
|
||||
public function downloadMultiple(DownloadMultipleDocumentsRequest $request)
|
||||
|
@ -16,6 +16,7 @@ use App\Events\Invoice\InvoiceWasViewed;
|
||||
use App\Events\Misc\InvitationWasViewed;
|
||||
use App\Events\Quote\QuoteWasViewed;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Payment;
|
||||
@ -106,15 +107,12 @@ class InvitationController extends Controller
|
||||
{
|
||||
switch ($entity_string) {
|
||||
case 'invoice':
|
||||
$invitation->invoice->service()->markSent()->save();
|
||||
event(new InvoiceWasViewed($invitation, $invitation->company, Ninja::eventVars()));
|
||||
break;
|
||||
case 'quote':
|
||||
$invitation->quote->service()->markSent()->save();
|
||||
event(new QuoteWasViewed($invitation, $invitation->company, Ninja::eventVars()));
|
||||
break;
|
||||
case 'credit':
|
||||
$invitation->credit->service()->markSent()->save();
|
||||
event(new CreditWasViewed($invitation, $invitation->company, Ninja::eventVars()));
|
||||
break;
|
||||
default:
|
||||
@ -125,9 +123,43 @@ class InvitationController extends Controller
|
||||
|
||||
public function routerForDownload(string $entity, string $invitation_key)
|
||||
{
|
||||
|
||||
if(Ninja::isHosted())
|
||||
return $this->returnRawPdf($entity, $invitation_key);
|
||||
|
||||
return redirect('client/'.$entity.'/'.$invitation_key.'/download_pdf');
|
||||
}
|
||||
|
||||
private function returnRawPdf(string $entity, string $invitation_key)
|
||||
{
|
||||
|
||||
$key = $entity.'_id';
|
||||
|
||||
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||
|
||||
$invitation = $entity_obj::whereRaw('BINARY `key`= ?', [$invitation_key])
|
||||
->with('contact.client')
|
||||
->firstOrFail();
|
||||
|
||||
if(!$invitation)
|
||||
return response()->json(["message" => "no record found"], 400);
|
||||
|
||||
$file_name = $invitation->{$entity}->numberFormatter().'.pdf';
|
||||
nlog($file_name);
|
||||
|
||||
$file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db);
|
||||
|
||||
$headers = ['Content-Type' => 'application/pdf'];
|
||||
|
||||
if(request()->input('inline') == 'true')
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
|
||||
}
|
||||
|
||||
public function routerForIframe(string $entity, string $client_hash, string $invitation_key)
|
||||
{
|
||||
}
|
||||
|
@ -500,7 +500,7 @@ class CompanyController extends BaseController
|
||||
$account->delete();
|
||||
|
||||
if(Ninja::isHosted())
|
||||
\Modules\Admin\Jobs\Account\NinjaDeletedAccount::dispatch($account_key);
|
||||
\Modules\Admin\Jobs\Account\NinjaDeletedAccount::dispatch($account_key, $request->all());
|
||||
|
||||
LightLogs::create(new AccountDeleted())
|
||||
->increment()
|
||||
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Filters\DocumentFilters;
|
||||
use App\Http\Requests\Document\DestroyDocumentRequest;
|
||||
use App\Http\Requests\Document\EditDocumentRequest;
|
||||
use App\Http\Requests\Document\ShowDocumentRequest;
|
||||
use App\Http\Requests\Document\StoreDocumentRequest;
|
||||
use App\Http\Requests\Document\UpdateDocumentRequest;
|
||||
@ -114,9 +115,16 @@ class DocumentController extends BaseController
|
||||
|
||||
public function download(ShowDocumentRequest $request, Document $document)
|
||||
{
|
||||
|
||||
$headers = [];
|
||||
|
||||
if(request()->input('inline') == 'true')
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
|
||||
return response()->streamDownload(function () use ($document) {
|
||||
echo file_get_contents($document->generateUrl());
|
||||
}, basename($document->generateUrl()));
|
||||
}, basename($document->generateUrl()), $headers);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,7 +134,7 @@ class DocumentController extends BaseController
|
||||
* @param Document $document
|
||||
* @return Response
|
||||
*/
|
||||
public function edit(EditDocumentRegquest $request, Document $document)
|
||||
public function edit(EditDocumentRequest $request, Document $document)
|
||||
{
|
||||
return $this->itemResponse($document);
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ class EmailController extends BaseController
|
||||
public function send(SendEmailRequest $request)
|
||||
{
|
||||
$entity = $request->input('entity');
|
||||
$entity_obj = $entity::find($request->input('entity_id'));
|
||||
$entity_obj = $entity::withTrashed()->with('invitations')->find($request->input('entity_id'));
|
||||
$subject = $request->has('subject') ? $request->input('subject') : '';
|
||||
$body = $request->has('body') ? $request->input('body') : '';
|
||||
$entity_string = strtolower(class_basename($entity_obj));
|
||||
|
@ -397,7 +397,7 @@ class InvoiceController extends BaseController
|
||||
|
||||
$invoice = $this->invoice_repo->save($request->all(), $invoice);
|
||||
|
||||
$invoice->service()->deletePdf();
|
||||
$invoice->service()->triggeredActions($request)->deletePdf();
|
||||
|
||||
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
|
@ -24,6 +24,7 @@ use App\Models\CompanyToken;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
@ -231,10 +232,23 @@ class MigrationController extends BaseController
|
||||
* @return \Illuminate\Http\JsonResponse|void
|
||||
*/
|
||||
public function startMigration(Request $request)
|
||||
{
|
||||
nlog("Starting Migration");
|
||||
{
|
||||
|
||||
$companies = json_decode($request->companies);
|
||||
// v4 Laravel 6
|
||||
|
||||
// $companies = [];
|
||||
|
||||
// foreach($request->all() as $input){
|
||||
|
||||
// if($input instanceof UploadedFile)
|
||||
// nlog('is file');
|
||||
// else
|
||||
// $companies[] = json_decode($input);
|
||||
// }
|
||||
|
||||
nlog("Starting Migration");
|
||||
|
||||
$companies = json_decode($request->companies,1);
|
||||
|
||||
if (app()->environment() === 'local') {
|
||||
nlog($request->all());
|
||||
@ -250,19 +264,31 @@ class MigrationController extends BaseController
|
||||
} finally {
|
||||
// Controller logic here
|
||||
|
||||
foreach ($companies as $company) {
|
||||
$is_valid = $request->file($company->company_index)->isValid();
|
||||
foreach($companies as $company)
|
||||
{
|
||||
|
||||
if (!$is_valid) {
|
||||
continue;
|
||||
}
|
||||
$company = (array)$company;
|
||||
|
||||
// v4 Laravel 6
|
||||
// $input = $request->all();
|
||||
|
||||
// foreach ($input as $company) {
|
||||
|
||||
// if($company instanceof UploadedFile)
|
||||
// continue;
|
||||
// else
|
||||
// $company = json_decode($company,1);
|
||||
|
||||
// if (!$company || !is_int($company['company_index'] || !$request->file($company['company_index'])->isValid())) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
$company_count = $user->account->companies()->count();
|
||||
|
||||
// Look for possible existing company (based on company keys).
|
||||
$existing_company = Company::whereRaw('BINARY `company_key` = ?', [$company->company_key])->first();
|
||||
$existing_company = Company::whereRaw('BINARY `company_key` = ?', [$company['company_key']])->first();
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
@ -291,7 +317,7 @@ class MigrationController extends BaseController
|
||||
|
||||
$checks = [
|
||||
'existing_company' => $existing_company ? (bool)1 : false,
|
||||
'force' => property_exists($company, 'force') ? (bool) $company->force : false,
|
||||
'force' => array_key_exists('force', $company) ? (bool) $company['force'] : false,
|
||||
];
|
||||
|
||||
// If there's existing company and ** no ** force is provided - skip migration.
|
||||
@ -378,10 +404,10 @@ class MigrationController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
$migration_file = $request->file($company->company_index)
|
||||
$migration_file = $request->file($company['company_index'])
|
||||
->storeAs(
|
||||
'migrations',
|
||||
$request->file($company->company_index)->getClientOriginalName(),
|
||||
$request->file($company['company_index'])->getClientOriginalName(),
|
||||
'public'
|
||||
);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\DataMapper\Analytics\EmailBounce;
|
||||
use App\DataMapper\Analytics\Mail\EmailBounce;
|
||||
use App\DataMapper\Analytics\Mail\EmailSpam;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Libraries\MultiDB;
|
||||
@ -74,7 +75,6 @@ class PostMarkController extends BaseController
|
||||
|
||||
if($request->header('X-API-SECURITY') && $request->header('X-API-SECURITY') == config('postmark.secret'))
|
||||
{
|
||||
// nlog($request->all());
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($request->input('Tag'));
|
||||
|
||||
@ -165,13 +165,13 @@ class PostMarkController extends BaseController
|
||||
$this->invitation->email_status = 'bounced';
|
||||
$this->invitation->save();
|
||||
|
||||
// $bounce = new EmailBounce(
|
||||
// $request->input('Tag'),
|
||||
// $request->input('From'),
|
||||
// $request->input('MessageID')
|
||||
// );
|
||||
$bounce = new EmailBounce(
|
||||
$request->input('Tag'),
|
||||
$request->input('From'),
|
||||
$request->input('MessageID')
|
||||
);
|
||||
|
||||
// LightLogs::create($bounce)->batch();
|
||||
LightLogs::create($bounce)->batch();
|
||||
|
||||
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
|
||||
}
|
||||
|
@ -212,7 +212,10 @@ class PreviewController extends BaseController
|
||||
}
|
||||
|
||||
$entity_obj = $repo->save($request->all(), $entity_obj);
|
||||
$entity_obj->service()->fillDefaults()->save();
|
||||
|
||||
if(!$request->has('entity_id'))
|
||||
$entity_obj->service()->fillDefaults()->save();
|
||||
|
||||
$entity_obj->load('client');
|
||||
|
||||
App::forgetInstance('translator');
|
||||
|
@ -391,7 +391,9 @@ class QuoteController extends BaseController
|
||||
|
||||
$quote = $this->quote_repo->save($request->all(), $quote);
|
||||
|
||||
$quote->service()->deletePdf();
|
||||
$quote->service()
|
||||
->triggeredActions($request)
|
||||
->deletePdf();
|
||||
|
||||
event(new QuoteWasUpdated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
|
@ -30,6 +30,7 @@ class SubdomainController extends BaseController
|
||||
'invoiceninja',
|
||||
'cname',
|
||||
'sandbox',
|
||||
'stage',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
@ -76,7 +76,7 @@ class SendingController extends Controller
|
||||
}
|
||||
|
||||
Mail::to(config('ninja.contact.ninja_official_contact'))
|
||||
->send(new SupportMessageSent($request->input('message'), $send_logs));
|
||||
->send(new SupportMessageSent($request->all(), $send_logs));
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
@ -47,6 +47,7 @@ class RecurringInvoicesTable extends Component
|
||||
->with('client')
|
||||
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||
->withTrashed()
|
||||
->where('is_deleted', false)
|
||||
->paginate($this->per_page);
|
||||
|
||||
return render('components.livewire.recurring-invoices-table', [
|
||||
|
@ -44,6 +44,7 @@ class UpdateCompanyRequest extends Request
|
||||
$rules['size_id'] = 'integer|nullable';
|
||||
$rules['country_id'] = 'integer|nullable';
|
||||
$rules['work_email'] = 'email|nullable';
|
||||
// $rules['client_registration_fields'] = 'array';
|
||||
|
||||
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
|
||||
$rules['portal_domain'] = 'sometimes|url';
|
||||
|
@ -82,7 +82,7 @@ class SendEmailRequest extends Request
|
||||
$entity = $input['entity'];
|
||||
|
||||
/* Harvest the entity*/
|
||||
$entity_obj = $entity::whereId($input['entity_id'])->company()->first();
|
||||
$entity_obj = $entity::whereId($input['entity_id'])->withTrashed()->company()->first();
|
||||
|
||||
/* Check object, check user and company id is same as users, and check user can edit the object */
|
||||
if ($entity_obj && ($company->id == $entity_obj->company_id) && auth()->user()->can('edit', $entity_obj)) {
|
||||
|
@ -794,6 +794,7 @@ class CompanyImport implements ShouldQueue
|
||||
['clients' => 'client_id'],
|
||||
['projects' => 'project_id'],
|
||||
['vendors' => 'vendor_id'],
|
||||
['invoices' => 'invoice_id'],
|
||||
],
|
||||
'expenses',
|
||||
'number');
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Jobs\Company;
|
||||
|
||||
use App\DataMapper\ClientRegistrationFields;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
@ -62,7 +63,8 @@ class CreateCompany
|
||||
$company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : '';
|
||||
$company->custom_fields = new \stdClass;
|
||||
$company->default_password_timeout = 1800000;
|
||||
|
||||
$company->client_registration_fields = ClientRegistrationFields::generate();
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$company->subdomain = MultiDB::randomSubdomainGenerator();
|
||||
else
|
||||
|
@ -104,9 +104,6 @@ class CreateEntityPdf implements ShouldQueue
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
|
||||
|
||||
/*This line of code hurts... it deletes ALL $entity PDFs... this causes a race condition when trying to send an email*/
|
||||
// $this->entity->service()->deletePdf();
|
||||
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->generate($this->invitation);
|
||||
}
|
||||
|
207
app/Jobs/Entity/CreateRawPdf.php
Normal file
207
app/Jobs/Entity/CreateRawPdf.php
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Entity Ninja (https://entityninja.com).
|
||||
*
|
||||
* @link https://github.com/entityninja/entityninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Entity Ninja LLC (https://entityninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Entity;
|
||||
|
||||
use App\Exceptions\FilePermissionsFailure;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Models\Credit;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CreateRawPdf implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash;
|
||||
|
||||
public $entity;
|
||||
|
||||
public $company;
|
||||
|
||||
public $contact;
|
||||
|
||||
public $invitation;
|
||||
|
||||
public $entity_string = '';
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param $invitation
|
||||
*/
|
||||
public function __construct($invitation, $db)
|
||||
{
|
||||
MultiDB::setDb($db);
|
||||
|
||||
$this->invitation = $invitation;
|
||||
|
||||
if ($invitation instanceof InvoiceInvitation) {
|
||||
$this->entity = $invitation->invoice;
|
||||
$this->entity_string = 'invoice';
|
||||
} elseif ($invitation instanceof QuoteInvitation) {
|
||||
$this->entity = $invitation->quote;
|
||||
$this->entity_string = 'quote';
|
||||
} elseif ($invitation instanceof CreditInvitation) {
|
||||
$this->entity = $invitation->credit;
|
||||
$this->entity_string = 'credit';
|
||||
} elseif ($invitation instanceof RecurringInvoiceInvitation) {
|
||||
$this->entity = $invitation->recurring_invoice;
|
||||
$this->entity_string = 'recurring_invoice';
|
||||
}
|
||||
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
/* Forget the singleton*/
|
||||
App::forgetInstance('translator');
|
||||
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
|
||||
|
||||
$entity_design_id = '';
|
||||
|
||||
if ($this->entity instanceof Invoice) {
|
||||
$path = $this->entity->client->invoice_filepath($this->invitation);
|
||||
$entity_design_id = 'invoice_design_id';
|
||||
} elseif ($this->entity instanceof Quote) {
|
||||
$path = $this->entity->client->quote_filepath($this->invitation);
|
||||
$entity_design_id = 'quote_design_id';
|
||||
} elseif ($this->entity instanceof Credit) {
|
||||
$path = $this->entity->client->credit_filepath($this->invitation);
|
||||
$entity_design_id = 'credit_design_id';
|
||||
} elseif ($this->entity instanceof RecurringInvoice) {
|
||||
$path = $this->entity->client->recurring_invoice_filepath($this->invitation);
|
||||
$entity_design_id = 'invoice_design_id';
|
||||
}
|
||||
|
||||
$file_path = $path.$this->entity->numberFormatter().'.pdf';
|
||||
|
||||
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->entity->client->getSetting($entity_design_id));
|
||||
|
||||
$design = Design::find($entity_design_id);
|
||||
|
||||
/* Catch all in case migration doesn't pass back a valid design */
|
||||
if(!$design)
|
||||
$design = Design::find(2);
|
||||
|
||||
$html = new HtmlEngine($this->invitation);
|
||||
|
||||
if ($design->is_custom) {
|
||||
$options = [
|
||||
'custom_partials' => json_decode(json_encode($design->design), true)
|
||||
];
|
||||
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
|
||||
} else {
|
||||
$template = new PdfMakerDesign(strtolower($design->name));
|
||||
}
|
||||
|
||||
$variables = $html->generateLabelsAndValues();
|
||||
|
||||
$state = [
|
||||
'template' => $template->elements([
|
||||
'client' => $this->entity->client,
|
||||
'entity' => $this->entity,
|
||||
'pdf_variables' => (array) $this->entity->company->settings->pdf_variables,
|
||||
'$product' => $design->design->product,
|
||||
'variables' => $variables,
|
||||
]),
|
||||
'variables' => $variables,
|
||||
'options' => [
|
||||
'all_pages_header' => $this->entity->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $this->entity->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMakerService($state);
|
||||
|
||||
$maker
|
||||
->design($template)
|
||||
->build();
|
||||
|
||||
$pdf = null;
|
||||
|
||||
try {
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$finfo = new \finfo(FILEINFO_MIME);
|
||||
|
||||
//fallback in case hosted PDF fails.
|
||||
if($finfo->buffer($pdf) != 'application/pdf; charset=binary')
|
||||
{
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
nlog(print_r($e->getMessage(), 1));
|
||||
}
|
||||
|
||||
if (config('ninja.log_pdf_html')) {
|
||||
info($maker->getCompiledHTML());
|
||||
}
|
||||
|
||||
if ($pdf)
|
||||
return $pdf;
|
||||
|
||||
throw new FilePermissionsFailure("Unable to generate the raw PDF");
|
||||
|
||||
}
|
||||
|
||||
public function failed($e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -132,7 +132,6 @@ class SendRecurring implements ShouldQueue
|
||||
});
|
||||
|
||||
if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $invoice->auto_bill_enabled) {
|
||||
|
||||
nlog("attempting to autobill {$invoice->number}");
|
||||
$invoice->service()->autoBill()->save();
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace App\Jobs\Util;
|
||||
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client as ClientModel;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\Webhook;
|
||||
use App\Transformers\ArraySerializer;
|
||||
@ -147,11 +148,12 @@ class WebhookHandler implements ShouldQueue
|
||||
|
||||
private function resolveClient()
|
||||
{
|
||||
if($this->entity->client()->exists()){
|
||||
//make sure it isn't an instance of the Client Model
|
||||
if((!$this->entity instanceof ClientModel) && $this->entity->client()->exists()){
|
||||
return $this->entity->client;
|
||||
}
|
||||
|
||||
return $this->company->clients->first();
|
||||
return $this->company->clients()->first();
|
||||
}
|
||||
|
||||
public function failed($exception)
|
||||
|
@ -41,6 +41,8 @@ class CreditViewedActivity implements ShouldQueue
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$event->invitation->credit->service()->markSent()->save();
|
||||
|
||||
$fields = new stdClass;
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->invitation->user_id;
|
||||
|
@ -45,6 +45,8 @@ class InvoiceViewedActivity implements ShouldQueue
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->invitation->invoice->user_id;
|
||||
|
||||
$event->invitation->invoice->service()->markSent()->save();
|
||||
|
||||
$fields->user_id = $user_id;
|
||||
$fields->company_id = $event->invitation->company_id;
|
||||
$fields->activity_type_id = Activity::VIEW_INVOICE;
|
||||
|
@ -41,6 +41,8 @@ class QuoteViewedActivity implements ShouldQueue
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$event->invitation->quote->service()->markSent()->save();
|
||||
|
||||
$fields = new stdClass;
|
||||
|
||||
$fields->user_id = $event->invitation->quote->user_id;
|
||||
|
@ -13,13 +13,13 @@ class SupportMessageSent extends Mailable
|
||||
{
|
||||
// use Queueable, SerializesModels;
|
||||
|
||||
public $support_message;
|
||||
public $data;
|
||||
|
||||
public $send_logs;
|
||||
|
||||
public function __construct($support_message, $send_logs)
|
||||
public function __construct(array $data, $send_logs)
|
||||
{
|
||||
$this->support_message = $support_message;
|
||||
$this->data = $data;
|
||||
$this->send_logs = $send_logs;
|
||||
}
|
||||
|
||||
@ -63,17 +63,19 @@ class SupportMessageSent extends Mailable
|
||||
$user = auth()->user();
|
||||
$db = str_replace("db-ninja-", "", $company->db);
|
||||
$is_large = $company->is_large ? "L" : "S";
|
||||
|
||||
$platform = array_key_exists('platform', $this->data) ? $this->data['platform'] : "U";
|
||||
$migrated = strlen($company->company_key) == 32 ? "M" : "";
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$subject = "{$priority}Hosted-{$db}-{$is_large} :: {$plan} :: ".date('M jS, g:ia');
|
||||
$subject = "{$priority}Hosted-{$db}-{$is_large}{$platform}{$migrated} :: {$plan} :: ".date('M jS, g:ia');
|
||||
else
|
||||
$subject = "{$priority}Self Hosted :: {$plan} :: ".date('M jS, g:ia');
|
||||
$subject = "{$priority}Self Hosted :: {$plan}{$platform} :: ".date('M jS, g:ia');
|
||||
|
||||
return $this->from(config('mail.from.address'), $user->present()->name())
|
||||
->replyTo($user->email, $user->present()->name())
|
||||
->subject($subject)
|
||||
->view('email.support.message', [
|
||||
'support_message' => $this->support_message,
|
||||
'support_message' => $this->data['support_message'],
|
||||
'system_info' => $system_info,
|
||||
'laravel_log' => $log_lines,
|
||||
'logo' => $company->present()->logo(),
|
||||
|
@ -407,7 +407,7 @@ class Account extends BaseModel
|
||||
}
|
||||
}
|
||||
catch(\Exception $e){
|
||||
\Sentry\captureMessage("I encountered an error with email quotas - defaulting to SEND");
|
||||
\Sentry\captureMessage("I encountered an error with email quotas for account {$this->key} - defaulting to SEND");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -96,6 +96,7 @@ class Company extends BaseModel
|
||||
'show_task_end_date',
|
||||
'use_comma_as_decimal_place',
|
||||
'report_include_drafts',
|
||||
'client_registration_fields',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@ -111,6 +112,7 @@ class Company extends BaseModel
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'client_registration_fields' => 'array',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
@ -485,7 +487,7 @@ class Company extends BaseModel
|
||||
{
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
if($this->portal_mode == 'domain')
|
||||
if($this->portal_mode == 'domain' && strlen($this->portal_domain) > 3)
|
||||
return $this->portal_domain;
|
||||
|
||||
return "https://{$this->subdomain}." . config('ninja.app_domain');
|
||||
|
@ -85,12 +85,14 @@ class Gateway extends StaticModel
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]];//eWay
|
||||
break;
|
||||
case 11:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false]];//Payfast
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]];//Payfast
|
||||
break;
|
||||
case 7:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie
|
||||
GatewayType::BANK_TRANSFER => ['refund' => false, 'token_billing' => true],
|
||||
GatewayType::KBC => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::BANCONTACT => ['refund' => false, 'token_billing' => false],
|
||||
];
|
||||
case 15:
|
||||
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal
|
||||
|
@ -25,6 +25,8 @@ class GatewayType extends StaticModel
|
||||
const APPLE_PAY = 8;
|
||||
const SEPA = 9;
|
||||
const CREDIT = 10;
|
||||
const KBC = 11;
|
||||
const BANCONTACT = 12;
|
||||
|
||||
public function gateway()
|
||||
{
|
||||
@ -41,32 +43,27 @@ class GatewayType extends StaticModel
|
||||
switch ($type) {
|
||||
case self::CREDIT_CARD:
|
||||
return ctrans('texts.credit_card');
|
||||
break;
|
||||
case self::BANK_TRANSFER:
|
||||
return ctrans('texts.bank_transfer');
|
||||
break;
|
||||
case self::PAYPAL:
|
||||
return ctrans('texts.paypal');
|
||||
break;
|
||||
case self::CRYPTO:
|
||||
return ctrans('texts.crypto');
|
||||
break;
|
||||
case self::CUSTOM:
|
||||
return ctrans('texts.custom');
|
||||
break;
|
||||
case self::ALIPAY:
|
||||
return ctrans('texts.alipay');
|
||||
break;
|
||||
case self::SOFORT:
|
||||
return ctrans('texts.sofort');
|
||||
break;
|
||||
case self::APPLE_PAY:
|
||||
return ctrans('texts.apple_pay');
|
||||
break;
|
||||
case self::SEPA:
|
||||
return ctrans('texts.sepa');
|
||||
break;
|
||||
|
||||
case self::KBC:
|
||||
return ctrans('texts.kbc_cbc');
|
||||
case self::BANCONTACT:
|
||||
return ctrans('texts.bancontact');
|
||||
|
||||
default:
|
||||
return 'Undefined.';
|
||||
break;
|
||||
|
@ -43,6 +43,8 @@ class PaymentType extends StaticModel
|
||||
const GOCARDLESS = 30;
|
||||
const CRYPTO = 31;
|
||||
const MOLLIE_BANK_TRANSFER = 34;
|
||||
const KBC = 35;
|
||||
const BANCONTACT = 36;
|
||||
|
||||
public static function parseCardType($cardName)
|
||||
{
|
||||
|
@ -164,4 +164,77 @@ class RecurringExpense extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function recurringDates()
|
||||
{
|
||||
|
||||
/* Return early if nothing to send back! */
|
||||
if ($this->status_id == RecurringInvoice::STATUS_COMPLETED ||
|
||||
$this->remaining_cycles == 0 ||
|
||||
!$this->next_send_date) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/* Endless - lets send 10 back*/
|
||||
$iterations = $this->remaining_cycles;
|
||||
|
||||
if ($this->remaining_cycles == -1) {
|
||||
$iterations = 10;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
|
||||
if (!Carbon::parse($this->next_send_date)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$next_send_date = Carbon::parse($this->next_send_date)->copy();
|
||||
|
||||
for ($x=0; $x<$iterations; $x++) {
|
||||
// we don't add the days... we calc the day of the month!!
|
||||
$this->nextDateByFrequency($next_send_date);
|
||||
|
||||
$data[] = [
|
||||
'send_date' => $next_send_date->format('Y-m-d'),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function nextDateByFrequency($date)
|
||||
{
|
||||
$offset = $this->client->timezone_offset();
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case RecurringInvoice::FREQUENCY_DAILY:
|
||||
return $date->startOfDay()->addDay()->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_WEEKLY:
|
||||
return $date->startOfDay()->addWeek()->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
|
||||
return $date->startOfDay()->addWeeks(2)->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
|
||||
return $date->startOfDay()->addWeeks(4)->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_MONTHLY:
|
||||
return $date->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
|
||||
return $date->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
|
||||
return $date->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
|
||||
return $date->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
|
||||
return $date->addMonthsNoOverflow(6)->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_ANNUALLY:
|
||||
return $date->startOfDay()->addYear()->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_TWO_YEARS:
|
||||
return $date->startOfDay()->addYears(2)->addSeconds($offset);
|
||||
case RecurringInvoice::FREQUENCY_THREE_YEARS:
|
||||
return $date->startOfDay()->addYears(3)->addSeconds($offset);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
$invoices->each(function ($invoice) use ($fee_total) {
|
||||
if (collect($invoice->line_items)->contains('type_id', '3')) {
|
||||
$invoice->service()->toggleFeesPaid()->deletePdf()->save();
|
||||
$invoice->service()->toggleFeesPaid()->save();
|
||||
$invoice->client->service()->updateBalance($fee_total)->save();
|
||||
$invoice->ledger()->updateInvoiceBalance($fee_total, "Gateway fee adjustment for invoice {$invoice->number}");
|
||||
}
|
||||
|
@ -119,6 +119,15 @@ class BraintreePaymentDriver extends BaseDriver
|
||||
]);
|
||||
|
||||
if ($result->success) {
|
||||
|
||||
$address = $this->gateway->address()->create([
|
||||
'customerId' => $result->customer->id,
|
||||
'firstName' => $this->client->present()->name,
|
||||
'streetAddress' => $this->client->address1,
|
||||
'postalCode' => $this->client->postal_code,
|
||||
'countryCodeAlpha2' => $this->client->country ? $this->client->country->iso_3166_2 : '',
|
||||
]);
|
||||
|
||||
return $result->customer;
|
||||
}
|
||||
}
|
||||
|
216
app/PaymentDrivers/Mollie/Bancontact.php
Normal file
216
app/PaymentDrivers/Mollie/Bancontact.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?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 App\PaymentDrivers\Mollie;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Jobs\Mail\PaymentFailureMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\PaymentDrivers\MolliePaymentDriver;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class Bancontact implements MethodInterface
|
||||
{
|
||||
protected MolliePaymentDriver $mollie;
|
||||
|
||||
public function __construct(MolliePaymentDriver $mollie)
|
||||
{
|
||||
$this->mollie = $mollie;
|
||||
|
||||
$this->mollie->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the authorization page for Bancontact.
|
||||
*
|
||||
* @param array $data
|
||||
* @return View
|
||||
*/
|
||||
public function authorizeView(array $data): View
|
||||
{
|
||||
return render('gateways.mollie.bancontact.authorize', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the authorization for Bancontact.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function authorizeResponse(Request $request): RedirectResponse
|
||||
{
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the payment page for Bancontact.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$this->mollie->payment_hash
|
||||
->withData('gateway_type_id', GatewayType::BANCONTACT)
|
||||
->withData('client_id', $this->mollie->client->id);
|
||||
|
||||
try {
|
||||
$payment = $this->mollie->gateway->payments->create([
|
||||
'method' => 'bancontact',
|
||||
'amount' => [
|
||||
'currency' => $this->mollie->client->currency()->code,
|
||||
'value' => $this->mollie->convertToMollieAmount((float) $this->mollie->payment_hash->data->amount_with_fee),
|
||||
],
|
||||
'description' => \sprintf('Invoices: %s', collect($data['invoices'])->pluck('invoice_number')),
|
||||
'redirectUrl' => route('client.payments.response', [
|
||||
'company_gateway_id' => $this->mollie->company_gateway->id,
|
||||
'payment_hash' => $this->mollie->payment_hash->hash,
|
||||
'payment_method_id' => GatewayType::BANCONTACT,
|
||||
]),
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->mollie->payment_hash->withData('payment_id', $payment->id);
|
||||
|
||||
return redirect(
|
||||
$payment->getCheckoutUrl()
|
||||
);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) {
|
||||
return $this->processUnsuccessfulPayment($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unsuccessful payment.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @throws PaymentFailed
|
||||
* @return void
|
||||
*/
|
||||
public function processUnsuccessfulPayment(\Exception $exception): void
|
||||
{
|
||||
PaymentFailureMailer::dispatch(
|
||||
$this->mollie->client,
|
||||
$exception->getMessage(),
|
||||
$this->mollie->client->company,
|
||||
$this->mollie->payment_hash->data->amount_with_fee
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$exception->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($exception->getMessage(), $exception->getCode());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the payments for the KBC.
|
||||
*
|
||||
* @param PaymentResponseRequest $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
if (!\property_exists($this->mollie->payment_hash->data, 'payment_id')) {
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed('Whoops, something went wrong. Missing required [payment_id] parameter. Please contact administrator. Reference hash: ' . $this->mollie->payment_hash->hash)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$payment = $this->mollie->gateway->payments->get(
|
||||
$this->mollie->payment_hash->data->payment_id
|
||||
);
|
||||
|
||||
if ($payment->status === 'paid') {
|
||||
return $this->processSuccessfulPayment($payment);
|
||||
}
|
||||
|
||||
if ($payment->status === 'open') {
|
||||
return $this->processOpenPayment($payment);
|
||||
}
|
||||
|
||||
if ($payment->status === 'failed') {
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed(ctrans('texts.status_failed'))
|
||||
);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed(ctrans('texts.status_voided'))
|
||||
);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) {
|
||||
return $this->processUnsuccessfulPayment($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the successful payment for Bancontact.
|
||||
*
|
||||
* @param string $status
|
||||
* @param ResourcesPayment $payment
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment, string $status = 'paid'): RedirectResponse
|
||||
{
|
||||
$data = [
|
||||
'gateway_type_id' => GatewayType::BANCONTACT,
|
||||
'amount' => array_sum(array_column($this->mollie->payment_hash->invoices(), 'amount')) + $this->mollie->payment_hash->fee_total,
|
||||
'payment_type' => PaymentType::BANCONTACT,
|
||||
'transaction_reference' => $payment->id,
|
||||
];
|
||||
|
||||
$payment_record = $this->mollie->createPayment(
|
||||
$data,
|
||||
$status === 'paid' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->mollie->encodePrimaryKey($payment_record->id)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle 'open' payment status for Bancontact.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processOpenPayment(\Mollie\Api\Resources\Payment $payment): RedirectResponse
|
||||
{
|
||||
return $this->processSuccessfulPayment($payment, 'open');
|
||||
}
|
||||
}
|
199
app/PaymentDrivers/Mollie/KBC.php
Normal file
199
app/PaymentDrivers/Mollie/KBC.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?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 App\PaymentDrivers\Mollie;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Jobs\Mail\PaymentFailureMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\PaymentDrivers\MolliePaymentDriver;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class KBC implements MethodInterface
|
||||
{
|
||||
protected MolliePaymentDriver $mollie;
|
||||
|
||||
public function __construct(MolliePaymentDriver $mollie)
|
||||
{
|
||||
$this->mollie = $mollie;
|
||||
|
||||
$this->mollie->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the authorization page for KBC.
|
||||
*
|
||||
* @param array $data
|
||||
* @return View
|
||||
*/
|
||||
public function authorizeView(array $data): View
|
||||
{
|
||||
return render('gateways.mollie.kbc.authorize', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the authorization for KBC.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function authorizeResponse(Request $request): RedirectResponse
|
||||
{
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the payment page for KBC.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$this->mollie->payment_hash
|
||||
->withData('gateway_type_id', GatewayType::KBC)
|
||||
->withData('client_id', $this->mollie->client->id);
|
||||
|
||||
try {
|
||||
$payment = $this->mollie->gateway->payments->create([
|
||||
'method' => 'kbc',
|
||||
'amount' => [
|
||||
'currency' => $this->mollie->client->currency()->code,
|
||||
'value' => $this->mollie->convertToMollieAmount((float) $this->mollie->payment_hash->data->amount_with_fee),
|
||||
],
|
||||
'description' => \sprintf('Invoices: %s', collect($data['invoices'])->pluck('invoice_number')),
|
||||
'redirectUrl' => route('client.payments.response', [
|
||||
'company_gateway_id' => $this->mollie->company_gateway->id,
|
||||
'payment_hash' => $this->mollie->payment_hash->hash,
|
||||
'payment_method_id' => GatewayType::KBC,
|
||||
]),
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->mollie->payment_hash->withData('payment_id', $payment->id);
|
||||
|
||||
return redirect(
|
||||
$payment->getCheckoutUrl()
|
||||
);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) {
|
||||
return $this->processUnsuccessfulPayment($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unsuccessful payment.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @throws PaymentFailed
|
||||
* @return void
|
||||
*/
|
||||
public function processUnsuccessfulPayment(\Exception $exception): void
|
||||
{
|
||||
PaymentFailureMailer::dispatch(
|
||||
$this->mollie->client,
|
||||
$exception->getMessage(),
|
||||
$this->mollie->client->company,
|
||||
$this->mollie->payment_hash->data->amount_with_fee
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$exception->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($exception->getMessage(), $exception->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the payments for the KBC.
|
||||
*
|
||||
* @param PaymentResponseRequest $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
if (! \property_exists($this->mollie->payment_hash->data, 'payment_id')) {
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed('Whoops, something went wrong. Missing required [payment_id] parameter. Please contact administrator. Reference hash: ' . $this->mollie->payment_hash->hash)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$payment = $this->mollie->gateway->payments->get(
|
||||
$this->mollie->payment_hash->data->payment_id
|
||||
);
|
||||
|
||||
if ($payment->status === 'paid') {
|
||||
return $this->processSuccessfulPayment($payment);
|
||||
}
|
||||
|
||||
if ($payment->status === 'failed') {
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed(ctrans('texts.status_failed'))
|
||||
);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed(ctrans('texts.status_voided'))
|
||||
);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) {
|
||||
return $this->processUnsuccessfulPayment($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the successful payment for KBC.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment): RedirectResponse
|
||||
{
|
||||
$data = [
|
||||
'gateway_type_id' => GatewayType::KBC,
|
||||
'amount' => array_sum(array_column($this->mollie->payment_hash->invoices(), 'amount')) + $this->mollie->payment_hash->fee_total,
|
||||
'payment_type' => PaymentType::KBC,
|
||||
'transaction_reference' => $payment->id,
|
||||
];
|
||||
|
||||
$payment_record = $this->mollie->createPayment(
|
||||
$data,
|
||||
$payment->status === 'paid' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->mollie->encodePrimaryKey($payment_record->id)]);
|
||||
}
|
||||
}
|
@ -24,8 +24,10 @@ use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Mollie\Bancontact;
|
||||
use App\PaymentDrivers\Mollie\BankTransfer;
|
||||
use App\PaymentDrivers\Mollie\CreditCard;
|
||||
use App\PaymentDrivers\Mollie\KBC;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Mollie\Api\Exceptions\ApiException;
|
||||
@ -65,7 +67,9 @@ class MolliePaymentDriver extends BaseDriver
|
||||
*/
|
||||
public static $methods = [
|
||||
GatewayType::CREDIT_CARD => CreditCard::class,
|
||||
GatewayType::BANCONTACT => Bancontact::class,
|
||||
GatewayType::BANK_TRANSFER => BankTransfer::class,
|
||||
GatewayType::KBC => KBC::class,
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_MOLLIE;
|
||||
@ -86,7 +90,9 @@ class MolliePaymentDriver extends BaseDriver
|
||||
$types = [];
|
||||
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
$types[] = GatewayType::BANCONTACT;
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
$types[] = GatewayType::KBC;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
@ -77,36 +77,47 @@ class Token
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
$amount = round(($amount * pow(10, $this->payfast->client->currency()->precision)),0);
|
||||
|
||||
// $header =[
|
||||
// 'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
// 'timestamp' => now()->format('c'),
|
||||
// 'version' => 'v1',
|
||||
// ];
|
||||
$header =[
|
||||
'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
'version' => 'v1',
|
||||
'timestamp' => now()->format('c'),
|
||||
];
|
||||
|
||||
// $body = [
|
||||
// 'amount' => $amount,
|
||||
// 'item_name' => 'purchase',
|
||||
// 'item_description' => ctrans('texts.invoices') . ': ' . collect($payment_hash->invoices())->pluck('invoice_number'),
|
||||
// // 'm_payment_id' => $payment_hash->hash,
|
||||
// ];
|
||||
$body = [
|
||||
'amount' => $amount,
|
||||
'item_name' => 'purchase',
|
||||
'item_description' => ctrans('texts.invoices') . ': ' . collect($payment_hash->invoices())->pluck('invoice_number'),
|
||||
'm_payment_id' => $payment_hash->hash,
|
||||
];
|
||||
|
||||
nlog(array_merge($body, $header));
|
||||
|
||||
// $header['signature'] = md5( $this->generate_parameter_string(array_merge($header, $body), false) );
|
||||
|
||||
// $result = $this->send($header, $body, $cgt->token);
|
||||
$api = new \PayFast\PayFastApi(
|
||||
[
|
||||
'merchantId' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
'passPhrase' => $this->payfast->company_gateway->getConfigField('passPhrase'),
|
||||
'testMode' => $this->payfast->company_gateway->getConfigField('testMode')
|
||||
]
|
||||
);
|
||||
$header['signature'] = $this->payfast->generateTokenSignature(array_merge($body, $header));
|
||||
|
||||
$adhocArray = $api
|
||||
->subscriptions
|
||||
->adhoc($cgt->token, ['amount' => $amount, 'item_name' => 'purchase']);
|
||||
nlog($header['signature']);
|
||||
|
||||
$result = $this->send($header, $body, $cgt->token);
|
||||
|
||||
nlog($result);
|
||||
|
||||
// $api = new \PayFast\PayFastApi(
|
||||
// [
|
||||
// 'merchantId' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
// 'passPhrase' => $this->payfast->company_gateway->getConfigField('passPhrase'),
|
||||
// 'testMode' => $this->payfast->company_gateway->getConfigField('testMode')
|
||||
// ]
|
||||
// );
|
||||
|
||||
// $adhocArray = $api
|
||||
// ->subscriptions
|
||||
// ->adhoc($cgt->token, ['amount' => $amount, 'item_name' => 'purchase']);
|
||||
|
||||
|
||||
// nlog($adhocArray);
|
||||
|
||||
|
||||
nlog($adhocArray);
|
||||
|
||||
// /*Refactor and push to BaseDriver*/
|
||||
// if ($data['response'] != null && $data['response']->getMessages()->getResultCode() == 'Ok') {
|
||||
@ -151,8 +162,8 @@ class Token
|
||||
protected function generate_parameter_string( $api_data, $sort_data_before_merge = true, $skip_empty_values = true ) {
|
||||
|
||||
// if sorting is required the passphrase should be added in before sort.
|
||||
if ( ! empty( $this->payfast->company_gateway->getConfigField('passPhrase') ) && $sort_data_before_merge )
|
||||
$api_data['passphrase'] = $this->payfast->company_gateway->getConfigField('passPhrase');
|
||||
if ( ! empty( $this->payfast->company_gateway->getConfigField('passphrase') ) && $sort_data_before_merge )
|
||||
$api_data['passphrase'] = $this->payfast->company_gateway->getConfigField('passphrase');
|
||||
|
||||
if ( $sort_data_before_merge ) {
|
||||
ksort( $api_data );
|
||||
@ -175,7 +186,7 @@ class Token
|
||||
if ( $sort_data_before_merge ) {
|
||||
$parameter_string = rtrim( $parameter_string, '&' );
|
||||
} elseif ( ! empty( $this->pass_phrase ) ) {
|
||||
$parameter_string .= 'passphrase=' . urlencode( $this->payfast->company_gateway->getConfigField('passPhrase') );
|
||||
$parameter_string .= 'passphrase=' . urlencode( $this->payfast->company_gateway->getConfigField('passphrase') );
|
||||
} else {
|
||||
$parameter_string = rtrim( $parameter_string, '&' );
|
||||
}
|
||||
@ -199,6 +210,8 @@ class Token
|
||||
}
|
||||
}
|
||||
|
||||
nlog(http_build_query($fields));
|
||||
|
||||
return md5(http_build_query($fields));
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class PayFastPaymentDriver extends BaseDriver
|
||||
|
||||
public $refundable = false; //does this gateway support refunds?
|
||||
|
||||
public $token_billing = false; //does this gateway support token billing?
|
||||
public $token_billing = true; //does this gateway support token billing?
|
||||
|
||||
public $can_authorise_credit_card = true; //does this gateway support authorizations?
|
||||
|
||||
@ -72,7 +72,7 @@ class PayFastPaymentDriver extends BaseDriver
|
||||
[
|
||||
'merchantId' => $this->company_gateway->getConfigField('merchantId'),
|
||||
'merchantKey' => $this->company_gateway->getConfigField('merchantKey'),
|
||||
'passPhrase' => $this->company_gateway->getConfigField('passPhrase'),
|
||||
'passPhrase' => $this->company_gateway->getConfigField('passphrase'),
|
||||
'testMode' => $this->company_gateway->getConfigField('testMode')
|
||||
]
|
||||
);
|
||||
@ -123,7 +123,39 @@ class PayFastPaymentDriver extends BaseDriver
|
||||
return (new Token($this))->tokenBilling($cgt, $payment_hash);
|
||||
}
|
||||
|
||||
public function generateSignature($data)
|
||||
public function generateTokenSignature($data)
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
$keys = [
|
||||
'merchant-id',
|
||||
'version',
|
||||
'timestamp',
|
||||
'amount',
|
||||
'item_name',
|
||||
'item_description',
|
||||
'itn',
|
||||
'm_payment_id',
|
||||
'cc_css',
|
||||
'split_payment'
|
||||
];
|
||||
|
||||
foreach($keys as $key)
|
||||
{
|
||||
if (!empty($data[$key])) {
|
||||
$fields[$key] = $data[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if($this->company_gateway->getConfigField('passphrase'))
|
||||
$fields['passphrase'] = $this->company_gateway->getConfigField('passphrase');
|
||||
|
||||
nlog(http_build_query($fields));
|
||||
|
||||
return md5(http_build_query($fields));
|
||||
}
|
||||
|
||||
public function generateSignature($data)
|
||||
{
|
||||
$fields = array();
|
||||
|
||||
|
@ -22,7 +22,9 @@ use App\Jobs\Util\SystemLogger;
|
||||
use App\Mail\Gateways\ACHVerificationNotification;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\StripePaymentDriver;
|
||||
@ -145,6 +147,62 @@ class ACH
|
||||
return render('gateways.stripe.ach.pay', $data);
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
} else {
|
||||
$description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
}
|
||||
|
||||
$this->stripe->init();
|
||||
|
||||
$response = null;
|
||||
|
||||
try {
|
||||
|
||||
$state = [
|
||||
'gateway_type_id' => GatewayType::BANK_TRANSFER,
|
||||
'amount' => $this->stripe->convertToStripeAmount($amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'currency' => $this->stripe->client->getCurrencyCode(),
|
||||
'customer' => $cgt->gateway_customer_reference,
|
||||
'source' => $cgt->token,
|
||||
];
|
||||
|
||||
$state['charge'] = \Stripe\Charge::create([
|
||||
'amount' => $state['amount'],
|
||||
'currency' => $state['currency'],
|
||||
'customer' => $state['customer'],
|
||||
'source' => $state['source'],
|
||||
'description' => $description,
|
||||
], $this->stripe->stripe_connect_auth);
|
||||
|
||||
|
||||
$payment_hash->data = array_merge((array)$payment_hash->data, $state);
|
||||
$payment_hash->save();
|
||||
|
||||
if ($state['charge']->status === 'pending' && is_null($state['charge']->failure_message)) {
|
||||
return $this->processPendingPayment($state, false);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment($state);
|
||||
} catch (Exception $e) {
|
||||
if ($e instanceof CardException) {
|
||||
return redirect()->route('client.payment_methods.verification', ['payment_method' => $source->hashed_id, 'method' => GatewayType::BANK_TRANSFER]);
|
||||
}
|
||||
|
||||
throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function paymentResponse($request)
|
||||
{
|
||||
@ -201,7 +259,7 @@ class ACH
|
||||
}
|
||||
}
|
||||
|
||||
public function processPendingPayment($state)
|
||||
public function processPendingPayment($state, $client_present = true)
|
||||
{
|
||||
$this->stripe->init();
|
||||
|
||||
@ -224,6 +282,9 @@ class ACH
|
||||
$this->stripe->client->company,
|
||||
);
|
||||
|
||||
if(!$client_present)
|
||||
return $payment;
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,13 @@ namespace App\PaymentDrivers\Stripe;
|
||||
use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\StripePaymentDriver;
|
||||
use App\PaymentDrivers\Stripe\ACH;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Stripe\Exception\ApiConnectionException;
|
||||
@ -51,6 +53,9 @@ class Charge
|
||||
*/
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
if($cgt->gateway_type_id == GatewayType::BANK_TRANSFER)
|
||||
return (new ACH($this->stripe))->tokenBilling($cgt, $payment_hash);
|
||||
|
||||
nlog(" DB = ".$this->stripe->client->company->db);
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
|
||||
|
@ -314,7 +314,9 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
$this->init();
|
||||
|
||||
$client_gateway_token = ClientGatewayToken::whereClientId($this->client->id)->whereCompanyGatewayId($this->company_gateway->id)->first();
|
||||
$client_gateway_token = ClientGatewayToken::whereClientId($this->client->id)
|
||||
->whereCompanyGatewayId($this->company_gateway->id)
|
||||
->first();
|
||||
|
||||
//Search by customer reference
|
||||
if ($client_gateway_token && $client_gateway_token->gateway_customer_reference) {
|
||||
|
@ -80,10 +80,10 @@ class ClientContactRepository extends BaseRepository
|
||||
});
|
||||
|
||||
//need to reload here to shake off stale contacts
|
||||
$client->load('contacts');
|
||||
$client->fresh();
|
||||
|
||||
//always made sure we have one blank contact to maintain state
|
||||
if ($client->contacts->count() == 0) {
|
||||
if ($client->contacts()->count() == 0) {
|
||||
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
$new_contact->client_id = $client->id;
|
||||
$new_contact->contact_key = Str::random(40);
|
||||
|
@ -24,6 +24,7 @@ use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\PdfMaker;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait;
|
||||
@ -110,6 +111,7 @@ class Statement
|
||||
DB::rollBack();
|
||||
}
|
||||
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,8 @@ class InvoiceService
|
||||
*/
|
||||
public function markPaid()
|
||||
{
|
||||
$this->removeUnpaidGatewayFees();
|
||||
|
||||
$this->invoice = (new MarkPaid($this->invoice))->run();
|
||||
|
||||
return $this;
|
||||
|
@ -605,6 +605,16 @@ class Design extends BaseDesign
|
||||
|
||||
public function tableTotals(): array
|
||||
{
|
||||
if ($this->type === self::STATEMENT) {
|
||||
return [
|
||||
['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [
|
||||
['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [
|
||||
['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']],
|
||||
]],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
$_variables = array_key_exists('variables', $this->context)
|
||||
? $this->context['variables']
|
||||
: ['values' => ['$entity.public_notes' => $this->entity->public_notes, '$entity.terms' => $this->entity->terms, '$entity_footer' => $this->entity->footer], 'labels' => []];
|
||||
@ -626,7 +636,7 @@ class Design extends BaseDesign
|
||||
['element' => 'div', 'properties' => ['class' => 'totals-table-right-side', 'dir' => '$dir'], 'elements' => []],
|
||||
];
|
||||
|
||||
if ($this->type == self::DELIVERY_NOTE || $this->type == self::STATEMENT) {
|
||||
if ($this->type == self::DELIVERY_NOTE) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
|
@ -217,6 +217,13 @@ trait DesignHelpers
|
||||
|
||||
public function sharedFooterElements()
|
||||
{
|
||||
// We want to show headers for statements, no exceptions.
|
||||
$statements = "
|
||||
document.querySelectorAll('#statement-invoice-table > thead > tr > th, #statement-payment-table > thead > tr > th, #statement-aging-table > thead > tr > th').forEach(t => {
|
||||
t.hidden = false;
|
||||
});
|
||||
";
|
||||
|
||||
// Unminified version, just for the reference.
|
||||
// By default all table headers are hidden with HTML `hidden` property.
|
||||
// This will check for table data values & if they're not empty it will remove hidden from the column itself.
|
||||
@ -259,6 +266,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
$html_decode = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(`[data-state="encoded-html"]`).forEach(e=>e.innerHTML=e.innerText)},!1);';
|
||||
|
||||
return ['element' => 'div', 'elements' => [
|
||||
['element' => 'script', 'content' => $statements],
|
||||
['element' => 'script', 'content' => $javascript],
|
||||
['element' => 'script', 'content' => $html_decode],
|
||||
]];
|
||||
|
@ -45,6 +45,10 @@ class TriggeredActions extends AbstractService
|
||||
$this->quote = $this->quote->service()->markSent()->save();
|
||||
}
|
||||
|
||||
if ($this->request->has('convert') && $this->request->input('convert') == 'true') {
|
||||
$this->quote = $this->quote->service()->convert()->save();
|
||||
}
|
||||
|
||||
|
||||
return $this->quote;
|
||||
}
|
||||
|
@ -27,13 +27,27 @@ class InvoiceHistoryTransformer extends EntityTransformer
|
||||
'activity',
|
||||
];
|
||||
|
||||
public function transform(Backup $backup)
|
||||
public function transform(?Backup $backup)
|
||||
{
|
||||
if(!$backup){
|
||||
|
||||
return [
|
||||
'id' => '',
|
||||
'activity_id' => '',
|
||||
'json_backup' => (string) '',
|
||||
'html_backup' => (string) '',
|
||||
'amount' => (float) 0,
|
||||
'created_at' => (int) 0,
|
||||
'updated_at' => (int) 0,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $this->encodePrimaryKey($backup->id),
|
||||
'activity_id' => $this->encodePrimaryKey($backup->activity_id),
|
||||
'json_backup' => (string) $backup->json_backup ?: '',
|
||||
'html_backup' => (string) $backup->html_backup ?: '',
|
||||
'json_backup' => (string) '',
|
||||
'html_backup' => (string) '',
|
||||
'amount' => (float) $backup->amount,
|
||||
'created_at' => (int) $backup->created_at,
|
||||
'updated_at' => (int) $backup->updated_at,
|
||||
|
@ -49,7 +49,7 @@ class RecurringExpenseTransformer extends EntityTransformer
|
||||
*/
|
||||
public function transform(RecurringExpense $recurring_expense)
|
||||
{
|
||||
return [
|
||||
$data = [
|
||||
'id' => $this->encodePrimaryKey($recurring_expense->id),
|
||||
'user_id' => $this->encodePrimaryKey($recurring_expense->user_id),
|
||||
'assigned_user_id' => $this->encodePrimaryKey($recurring_expense->assigned_user_id),
|
||||
@ -101,6 +101,12 @@ class RecurringExpenseTransformer extends EntityTransformer
|
||||
'remaining_cycles' => (int) $recurring_expense->remaining_cycles,
|
||||
'last_sent_date' => $recurring_expense->last_sent_date ?: '',
|
||||
'next_send_date' => $recurring_expense->next_send_date ?: '',
|
||||
'recurring_dates' => (array) [],
|
||||
];
|
||||
|
||||
if(request()->has('show_dates') && request()->query('show_dates') == 'true')
|
||||
$data['recurring_dates'] = (array) $recurring_expense->recurringDates();
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -127,7 +127,7 @@ class RecurringInvoiceTransformer extends EntityTransformer
|
||||
'due_date_days' => (string) $invoice->due_date_days ?: '',
|
||||
'paid_to_date' => (float) $invoice->paid_to_date,
|
||||
'subscription_id' => (string)$this->encodePrimaryKey($invoice->subscription_id),
|
||||
|
||||
'recurring_dates' => (array) [],
|
||||
];
|
||||
|
||||
|
||||
|
@ -158,6 +158,7 @@ class HtmlEngine
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')];
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
$data['$viewButton'] = &$data['$view_link'];
|
||||
$data['$view_button'] = &$data['$view_link'];
|
||||
$data['$approveButton'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.approve')];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
@ -171,6 +172,7 @@ class HtmlEngine
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')];
|
||||
$data['$viewButton'] = &$data['$view_link'];
|
||||
$data['$view_button'] = &$data['$view_link'];
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
|
||||
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
|
||||
|
@ -48,7 +48,7 @@ trait Inviteable
|
||||
$entity_type = Str::snake(class_basename($this->entityType()));
|
||||
|
||||
if(Ninja::isHosted()){
|
||||
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
|
||||
$domain = $this->company->domain();
|
||||
}
|
||||
else
|
||||
$domain = config('ninja.app_url');
|
||||
@ -75,7 +75,7 @@ trait Inviteable
|
||||
{
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
|
||||
$domain = $this->company->domain();
|
||||
else
|
||||
$domain = config('ninja.app_url');
|
||||
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.3.17',
|
||||
'app_tag' => '5.3.17',
|
||||
'app_version' => '5.3.18',
|
||||
'app_tag' => '5.3.18',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddKbcToPaymentTypes extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$type = new PaymentType();
|
||||
|
||||
$type->id = 35;
|
||||
$type->name = 'KBC/CBC';
|
||||
$type->gateway_type_id = GatewayType::KBC;
|
||||
|
||||
$type->save();
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddBancontactToPaymentTypes extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$type = new PaymentType();
|
||||
|
||||
$type->id = 36;
|
||||
$type->name = 'Bancontact';
|
||||
$type->gateway_type_id = GatewayType::BANCONTACT;
|
||||
|
||||
$type->save();
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
use App\DataMapper\ClientRegistrationFields;
|
||||
use App\Models\Company;
|
||||
use App\Models\Currency;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddRequiredClientRegistrationFields extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->mediumText('client_registration_fields')->nullable();
|
||||
});
|
||||
|
||||
Company::all()->each(function ($company){
|
||||
$company->update(['client_registration_fields' => ClientRegistrationFields::generate()]);
|
||||
});
|
||||
|
||||
|
||||
Model::unguard();
|
||||
|
||||
|
||||
$currencies = [
|
||||
['id' => 111, 'name' => 'Cuban Peso','code' => 'CUP', 'symbol' => '₱', 'precision' => '2','thousand_separator' => ',','decimal_separator' => '.'],
|
||||
|
||||
];
|
||||
|
||||
foreach ($currencies as $currency) {
|
||||
$record = Currency::whereCode($currency['code'])->first();
|
||||
if ($record) {
|
||||
$record->name = $currency['name'];
|
||||
$record->symbol = $currency['symbol'];
|
||||
$record->precision = $currency['precision'];
|
||||
$record->thousand_separator = $currency['thousand_separator'];
|
||||
$record->decimal_separator = $currency['decimal_separator'];
|
||||
if (isset($currency['swap_currency_symbol'])) {
|
||||
$record->swap_currency_symbol = $currency['swap_currency_symbol'];
|
||||
}
|
||||
$record->save();
|
||||
} else {
|
||||
Currency::create($currency);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -4,7 +4,7 @@ const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"/": "d54a2d8f5df9a52b1936136260e327b3",
|
||||
"/": "046e0fcccb3cc2bf087c3ca7100aab14",
|
||||
"assets/NOTICES": "9eb7e2eb2888ea5bae5f536720db37cd",
|
||||
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
|
||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||
@ -34,7 +34,7 @@ const RESOURCES = {
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"main.dart.js": "d0bf26e6fb4226713654b97cdfef6a53"
|
||||
"main.dart.js": "cad794ed29ee69bb27294af52f923f50"
|
||||
};
|
||||
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
|
244612
public/main.dart.js
vendored
244612
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
241535
public/main.foss.dart.js
vendored
241535
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
246369
public/main.last.dart.js
vendored
246369
public/main.last.dart.js
vendored
File diff suppressed because one or more lines are too long
251530
public/main.next.dart.js
vendored
251530
public/main.next.dart.js
vendored
File diff suppressed because one or more lines are too long
20343
public/main.profile.dart.js
vendored
20343
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
239614
public/main.wasm.dart.js
vendored
239614
public/main.wasm.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||
"/css/app.css": "/css/app.css?id=08bae341ed680d6ba544",
|
||||
"/css/app.css": "/css/app.css?id=ad5ff55c9ef56ede1726",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
||||
|
@ -4314,6 +4314,8 @@ $LANG = array(
|
||||
'generic_gateway_error' => 'Gateway configuration error. Please check your credentials.',
|
||||
'my_documents' => 'My documents',
|
||||
'payment_method_cannot_be_preauthorized' => 'This payment method cannot be preauthorized.',
|
||||
'kbc_cbc' => 'KBC/CBC',
|
||||
'bancontact' => 'Bancontact',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-user-agent="{{ $user_agent }}">
|
||||
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-user-agent="{{ $user_agent }}" data-login="{{ $login }}">
|
||||
<head>
|
||||
<!-- Source: https://github.com/invoiceninja/invoiceninja -->
|
||||
<!-- Version: {{ config('ninja.app_version') }} -->
|
||||
|
@ -73,6 +73,7 @@
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.state')])
|
||||
<select class="input w-full" id="billing-region">
|
||||
<option disabled selected></option>
|
||||
@foreach(\App\DataProviders\USStates::get() as $code => $state)
|
||||
<option value="{{ $code }}">{{ $state }} ({{ $code }})</option>
|
||||
@endforeach
|
||||
|
@ -10,15 +10,8 @@
|
||||
<style>*, *::after, *::before {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #FFF;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
|
||||
}
|
||||
|
||||
#payment-form {
|
||||
width: 31.5rem;
|
||||
margin: 0 auto
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
@ -164,7 +157,7 @@
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<div id="checkout--container">
|
||||
<form id="payment-form" method="POST" action="#">
|
||||
<form class="xl:flex xl:justify-center" id="payment-form" method="POST" action="#">
|
||||
<div class="one-liner">
|
||||
<div class="card-frame">
|
||||
<!-- form will be added here -->
|
||||
|
@ -0,0 +1,8 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Bancontact', 'card_title' =>
|
||||
'Bancontact'])
|
||||
|
||||
@section('gateway_content')
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
{{ __('texts.payment_method_cannot_be_preauthorized') }}
|
||||
@endcomponent
|
||||
@endsection
|
@ -0,0 +1,8 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'KBC/CBC', 'card_title' =>
|
||||
'KBC/CBC'])
|
||||
|
||||
@section('gateway_content')
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
{{ __('texts.payment_method_cannot_be_preauthorized') }}
|
||||
@endcomponent
|
||||
@endsection
|
@ -43,7 +43,8 @@
|
||||
@endcomponent
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.country')])
|
||||
<select name="countries" id="country" class="form-select input w-full" required>
|
||||
<select name="countries" id="country" class="form-select input w-full">
|
||||
<option disabled selected></option>
|
||||
@foreach($countries as $country)
|
||||
<option value="{{ $country->iso_3166_2 }}">{{ $country->iso_3166_2 }} ({{ $country->name }})</option>
|
||||
@endforeach
|
||||
@ -52,6 +53,7 @@
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.currency')])
|
||||
<select name="currencies" id="currency" class="form-select input w-full">
|
||||
<option disabled selected></option>
|
||||
@foreach($currencies as $currency)
|
||||
<option value="{{ $currency->code }}">{{ $currency->code }} ({{ $currency->name }})</option>
|
||||
@endforeach
|
||||
|
@ -14,7 +14,7 @@
|
||||
{{ ctrans('texts.first_name') }}
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="text" class="input w-full" name="first_name" value="{{ old('first_name') }}">
|
||||
<input type="text" class="input w-full" name="first_name" value="{{ old('first_name') }}" required>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
@ -22,7 +22,7 @@
|
||||
{{ ctrans('texts.last_name') }}
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="text" class="input w-full" name="last_name" value="{{ old('last_name') }}">
|
||||
<input type="text" class="input w-full" name="last_name" value="{{ old('last_name') }}"required>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
@ -30,7 +30,7 @@
|
||||
{{ ctrans('texts.email') }}
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="email" class="input w-full" name="email" value="{{ old('email') }}">
|
||||
<input type="email" class="input w-full" name="email" value="{{ old('email') }}"required>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:flex sm:items-center">
|
||||
@ -38,7 +38,7 @@
|
||||
{{ ctrans('texts.password') }}
|
||||
</dt>
|
||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="password" class="input w-full" name="password">
|
||||
<input type="password" class="input w-full" name="password"required>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
@ -53,7 +53,7 @@
|
||||
<div class="alert py-2 bg-white" id="test-pdf-response"></div>
|
||||
</dd>
|
||||
<a target="_blank" class="block text-sm text-gray-900 leading-5 underline"
|
||||
href="https://invoiceninja.github.io/selfhost.html#phantom-js">
|
||||
href="https://invoiceninja.github.io/docs/self-host-troubleshooting/#pdf-conversion-issues">
|
||||
{{ ctrans('texts.setup_phantomjs_note') }}
|
||||
</a>
|
||||
</div>
|
||||
|
2
storage/app/public/.gitignore
vendored
Executable file
2
storage/app/public/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
102
tests/Browser/ClientPortal/Gateways/Mollie/BancontactTest.php
Normal file
102
tests/Browser/ClientPortal/Gateways/Mollie/BancontactTest.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?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\Browser\ClientPortal\Gateways\Mollie;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
use Tests\Browser\Pages\ClientPortal\Login;
|
||||
|
||||
class BancontactTest extends DuskTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
foreach (static::$browsers as $browser) {
|
||||
$browser->driver->manage()->deleteAllCookies();
|
||||
}
|
||||
|
||||
$this->disableCompanyGateways();
|
||||
|
||||
CompanyGateway::where('gateway_key', '1bd651fb213ca0c9d66ae3c336dc77e8')->restore();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visit(new Login())
|
||||
->auth();
|
||||
});
|
||||
}
|
||||
|
||||
public function testSuccessfulPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Bancontact')
|
||||
->waitForText('Test profile')
|
||||
->radio('final_state', 'paid')
|
||||
->press('Continue')
|
||||
->waitForText('Details of the payment')
|
||||
->assertSee('Completed');
|
||||
});
|
||||
}
|
||||
|
||||
public function testOpenPayments(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Bancontact')
|
||||
->waitForText('Test profile')
|
||||
->radio('final_state', 'open')
|
||||
->press('Continue')
|
||||
->waitForText('Details of the payment')
|
||||
->assertSee('Pending');
|
||||
});
|
||||
}
|
||||
|
||||
public function testFailedPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Bancontact')
|
||||
->waitForText('Test profile')
|
||||
->radio('final_state', 'failed')
|
||||
->press('Continue')
|
||||
->waitForText('Failed.');
|
||||
});
|
||||
}
|
||||
|
||||
public function testCancelledTest(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Bancontact')
|
||||
->waitForText('Test profile')
|
||||
->radio('final_state', 'canceled')
|
||||
->press('Continue')
|
||||
->waitForText('Cancelled.');
|
||||
});
|
||||
}
|
||||
}
|
89
tests/Browser/ClientPortal/Gateways/Mollie/KBCTest.php
Normal file
89
tests/Browser/ClientPortal/Gateways/Mollie/KBCTest.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?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\Browser\ClientPortal\Gateways\Mollie;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
use Tests\Browser\Pages\ClientPortal\Login;
|
||||
|
||||
class KBCTest extends DuskTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
foreach (static::$browsers as $browser) {
|
||||
$browser->driver->manage()->deleteAllCookies();
|
||||
}
|
||||
|
||||
$this->disableCompanyGateways();
|
||||
|
||||
CompanyGateway::where('gateway_key', '1bd651fb213ca0c9d66ae3c336dc77e8')->restore();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visit(new Login())
|
||||
->auth();
|
||||
});
|
||||
}
|
||||
|
||||
public function testSuccessfulPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Undefined.')
|
||||
->waitForText('Test profile')
|
||||
->press('CBC')
|
||||
->radio('final_state', 'paid')
|
||||
->press('Continue')
|
||||
->waitForText('Details of the payment')
|
||||
->assertSee('Completed');
|
||||
});
|
||||
}
|
||||
|
||||
public function testFailedPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Undefined.')
|
||||
->waitForText('Test profile')
|
||||
->press('CBC')
|
||||
->radio('final_state', 'failed')
|
||||
->press('Continue')
|
||||
->waitForText('Failed.');
|
||||
});
|
||||
}
|
||||
|
||||
public function testCancelledTest(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Undefined.')
|
||||
->waitForText('Test profile')
|
||||
->press('CBC')
|
||||
->radio('final_state', 'canceled')
|
||||
->press('Continue')
|
||||
->waitForText('Cancelled.');
|
||||
});
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user