Merge branch 'v5-develop' into v5-stable

This commit is contained in:
David Bomba 2022-07-02 12:39:53 +10:00
commit ef2a8b832f
98 changed files with 489407 additions and 471748 deletions

View File

@ -1 +1 @@
5.4.4
5.4.7

View File

@ -25,6 +25,9 @@ class CompanySettings extends BaseSettings
/*Invoice*/
public $auto_archive_invoice = false; // @implemented
public $qr_iban = ''; //@implemented
public $besr_id = ''; //@implemented
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented
public $enable_client_portal_tasks = false; //@ben to implement
@ -289,6 +292,8 @@ class CompanySettings extends BaseSettings
public $auto_archive_invoice_cancelled = false;
public static $casts = [
'besr_id' => 'string',
'qr_iban' => 'string',
'email_subject_purchase_order' => 'string',
'email_template_purchase_order' => 'string',
'require_purchase_order_signature' => 'bool',

View File

@ -11,6 +11,7 @@
namespace App\Factory;
use App\Models\CompanyUser;
use App\Models\User;
class UserFactory

View File

@ -138,6 +138,14 @@ class InvoiceFilters extends QueryFilters
});
}
public function without_deleted_clients()
{
return $this->builder->whereHas('client', function ($query) {
$query->where('is_deleted',0);
});
}
public function upcoming()
{
return $this->builder
@ -212,7 +220,7 @@ class InvoiceFilters extends QueryFilters
{
if (auth()->guard('contact')->user()) {
return $this->contactViewFilter();
} else {
} else {
return $this->builder->company()->with(['invitations.company'], ['documents.company']);
}

View File

@ -0,0 +1,151 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Helpers\SwissQr;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use Sprain\SwissQrBill as QrBill;
/**
* SwissQrGenerator.
*/
class SwissQrGenerator
{
protected Company $company;
protected $invoice;
protected Client $client;
public function __construct($invoice, Company $company)
{
$this->company = $company;
$this->invoice = $invoice;
$this->client = $invoice->client;
}
private function calcDueAmount()
{
if($this->invoice->partial > 0)
return $this->invoice->partial;
if($this->invoice->status_id == Invoice::STATUS_DRAFT)
return $this->invoice->amount;
return $this->invoice->balance;
}
public function run()
{
// This is an example how to create a typical qr bill:
// - with reference number
// - with known debtor
// - with specified amount
// - with human-readable additional information
// - using your QR-IBAN
//
// Likely the most common use-case in the business world.
// Create a new instance of QrBill, containing default headers with fixed values
$qrBill = QrBill\QrBill::create();
// Add creditor information
// Who will receive the payment and to which bank account?
$qrBill->setCreditor(
QrBill\DataGroup\Element\CombinedAddress::create(
$this->company->present()->name(),
$this->company->present()->address1(),
$this->company->present()->getCompanyCityState(),
'CH'
));
$qrBill->setCreditorInformation(
QrBill\DataGroup\Element\CreditorInformation::create(
$this->company->present()->qr_iban() ?: '' // This is a special QR-IBAN. Classic IBANs will not be valid here.
));
// Add debtor information
// Who has to pay the invoice? This part is optional.
//
// Notice how you can use two different styles of addresses: CombinedAddress or StructuredAddress
// They are interchangeable for creditor as well as debtor.
$qrBill->setUltimateDebtor(
QrBill\DataGroup\Element\StructuredAddress::createWithStreet(
substr($this->client->present()->name(), 0 , 70),
$this->client->address1 ? substr($this->client->address1, 0 , 70) : '',
$this->client->address2 ? substr($this->client->address2, 0 , 16) : '',
$this->client->postal_code ? substr($this->client->postal_code, 0, 16) : '',
$this->client->city ? substr($this->client->postal_code, 0, 35) : '',
'CH'
));
// Add payment amount information
// What amount is to be paid?
$qrBill->setPaymentAmountInformation(
QrBill\DataGroup\Element\PaymentAmountInformation::create(
'CHF',
$this->calcDueAmount()
));
// Add payment reference
// This is what you will need to identify incoming payments.
$referenceNumber = QrBill\Reference\QrPaymentReferenceGenerator::generate(
$this->company->present()->besr_id() ?: '', // You receive this number from your bank (BESR-ID). Unless your bank is PostFinance, in that case use NULL.
$this->invoice->number// A number to match the payment with your internal data, e.g. an invoice number
);
$qrBill->setPaymentReference(
QrBill\DataGroup\Element\PaymentReference::create(
QrBill\DataGroup\Element\PaymentReference::TYPE_QR,
$referenceNumber
));
// Optionally, add some human-readable information about what the bill is for.
$qrBill->setAdditionalInformation(
QrBill\DataGroup\Element\AdditionalInformation::create(
$this->invoice->public_notes ?: ''
)
);
// Now get the QR code image and save it as a file.
try {
$output = new QrBill\PaymentPart\Output\HtmlOutput\HtmlOutput($qrBill, 'en');
$html = $output
->setPrintable(false)
->getPaymentPart();
return $html;
} catch (\Exception $e) {
foreach($qrBill->getViolations() as $key => $violation) {
nlog("qr");
nlog($violation);
}
return '';
// return $e->getMessage();
}
}
}

View File

@ -332,11 +332,6 @@ class LoginController extends BaseController
if (request()->input('provider') == 'google') {
return $this->handleGoogleOauth();
} elseif (request()->input('provider') == 'microsoft') {
// if (request()->has('token')) {
// return $this->handleSocialiteLogin('microsoft', request()->get('token'));
// } else {
// $message = 'Bearer token missing for the microsoft login';
// }
return $this->handleMicrosoftOauth();
} elseif (request()->input('provider') == 'apple') {
// if (request()->has('token')) {
@ -498,8 +493,10 @@ class LoginController extends BaseController
{
if(request()->has('accessToken'))
$accessToken = request()->input('accessToken');
elseif(request()->has('access_token'))
$accessToken = request()->input('access_token');
else
return response()->json(['message' => 'Invalid response from oauth server'], 400);
return response()->json(['message' => 'Invalid response from oauth server, no access token in response.'], 400);
$graph = new \Microsoft\Graph\Graph();
$graph->setAccessToken($accessToken);
@ -510,7 +507,6 @@ class LoginController extends BaseController
if($user){
$account = request()->input('account');
$email = $user->getMail() ?: $user->getUserPrincipalName();
$query = [
@ -551,6 +547,8 @@ class LoginController extends BaseController
}
return response()->json(['message' => 'Unable to authenticate this user'], 400);
}
private function existingOauthUser($existing_user)
@ -698,7 +696,7 @@ class LoginController extends BaseController
}
if($provider == 'microsoft'){
$scopes = ['email', 'Mail.ReadWrite', 'Mail.Send', 'offline_access', 'profile', 'User.Read openid'];
$scopes = ['email', 'Mail.Send', 'offline_access', 'profile', 'User.Read openid'];
$parameters = ['response_type' => 'code', 'redirect_uri' => config('ninja.app_url')."/auth/microsoft"];
}
@ -770,6 +768,8 @@ class LoginController extends BaseController
$oauth_user_token = $socialite_user->accessTokenResponseBody['access_token'];
$oauth_expiry = now()->addSeconds($socialite_user->accessTokenResponseBody['expires_in']) ?: now()->addSeconds(300);
if($user = OAuth::handleAuth($socialite_user, $provider))
{
@ -783,7 +783,8 @@ class LoginController extends BaseController
'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider,
'oauth_user_token' => $oauth_user_token,
'oauth_user_refresh_token' => $socialite_user->accessTokenResponseBody['refresh_token']
'oauth_user_refresh_token' => $socialite_user->accessTokenResponseBody['refresh_token'],
'oauth_user_token_expiry' => $oauth_expiry,
];
$user->update($update_user);

View File

@ -79,6 +79,7 @@ class BaseController extends Controller
'company.groups.documents',
'company.invoices.invitations.contact',
'company.invoices.invitations.company',
'company.purchase_orders.invitations',
'company.invoices.documents',
'company.products',
'company.products.documents',
@ -778,8 +779,13 @@ class BaseController extends Controller
}
/* Clean up URLs and remove query parameters from the URL*/
if(request()->has('login') && request()->input('login') == 'true')
return redirect('/')->with(['login' => "true"]);
if (request()->has('login') && request()->input('login') == 'true') {
return redirect('/')->with(['login' => 'true']);
}
if (request()->has('signup') && request()->input('signup') == 'true') {
return redirect('/')->with(['signup' => 'true']);
}
$data = [];
@ -789,10 +795,16 @@ 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";
if(request()->session()->has('login'))
$data['login'] = "true";
$data['login'] = request()->has('login') ? request()->input('login') : 'false';
$data['signup'] = request()->has('signup') ? request()->input('signup') : 'false';
if (request()->session()->has('login')) {
$data['login'] = 'true';
}
if(request()->session()->has('signup')){
$data['signup'] = 'true';
}
$data['user_agent'] = request()->server('HTTP_USER_AGENT');

View File

@ -235,6 +235,9 @@ class InvitationController extends Controller
->with('contact.client')
->firstOrFail();
if($invitation->contact->trashed())
$invitation->contact->restore();
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
$invoice = $invitation->invoice;

View File

@ -25,6 +25,7 @@ use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Models\Subscription;
use App\Notifications\Ninja\NewAccountNotification;
use App\Repositories\SubscriptionRepository;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
@ -165,6 +166,9 @@ class NinjaPlanController extends Controller
->increment()
->queue();
$ninja_company = Company::on('db-ninja-01')->find(config('ninja.ninja_default_company_id'));
$ninja_company->notification(new NewAccountNotification($account, $client))->ninja();
return $this->render('plan.trial_confirmed', $data);
}

View File

@ -190,7 +190,7 @@ class QuoteController extends Controller
if ($process) {
foreach ($quotes as $quote) {
$quote->service()->approve(auth()->user())->save();
event(new QuoteWasApproved(auth()->guard('contact')->user(), $quote, $quote->company, Ninja::eventVars()));
// event(new QuoteWasApproved(auth()->guard('contact')->user(), $quote, $quote->company, Ninja::eventVars()));
if (request()->has('signature') && !is_null(request()->signature) && !empty(request()->signature)) {
InjectSignature::dispatch($quote, request()->signature);

View File

@ -22,6 +22,7 @@ use Google_Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Microsoft\Graph\Model;
class ConnectedAccountController extends BaseController
{
@ -81,12 +82,61 @@ class ConnectedAccountController extends BaseController
return $this->handleGoogleOauth();
}
if ($request->input('provider') == 'microsoft') {
return $this->handleMicrosoftOauth($request);
}
return response()
->json(['message' => 'Provider not supported'], 400)
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
private function handleMicrosoftOauth($request)
{
nlog($request->all());
if(!$request->has('access_token'))
return response()->json(['message' => 'No access_token parameter found!'], 400);
$graph = new \Microsoft\Graph\Graph();
$graph->setAccessToken($request->input('access_token'));
$user = $graph->createRequest("GET", "/me")
->setReturnType(Model\User::class)
->execute();
if($user){
$email = $user->getMail() ?: $user->getUserPrincipalName();
if(auth()->user()->email != $email && MultiDB::checkUserEmailExists($email))
return response()->json(['message' => ctrans('texts.email_already_register')], 400);
$connected_account = [
'email' => $email,
'oauth_user_id' => $user->getId(),
'oauth_provider_id' => 'microsoft',
'email_verified_at' =>now()
];
auth()->user()->update($connected_account);
auth()->user()->email_verified_at = now();
auth()->user()->save();
$this->setLoginCache(auth()->user());
return $this->itemResponse(auth()->user());
}
return response()
->json(['message' => ctrans('texts.invalid_credentials')], 401)
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
private function handleGoogleOauth()
{
$user = false;

View File

@ -284,12 +284,11 @@ class PreviewController extends BaseController
}
catch(\Exception $e){
nlog($e->getMessage());
DB::connection(config('database.default'))->rollBack();
return;
}
//if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));

View File

@ -0,0 +1,470 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers;
use App\DataMapper\Analytics\LivePreview;
use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory;
use App\Factory\PurchaseOrderFactory;
use App\Factory\QuoteFactory;
use App\Factory\RecurringInvoiceFactory;
use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\Requests\Preview\PreviewInvoiceRequest;
use App\Http\Requests\Preview\PreviewPurchaseOrderRequest;
use App\Jobs\Util\PreviewPdf;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Repositories\CreditRepository;
use App\Repositories\InvoiceRepository;
use App\Repositories\PurchaseOrderRepository;
use App\Repositories\QuoteRepository;
use App\Repositories\RecurringInvoiceRepository;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\Design;
use App\Services\PdfMaker\PdfMaker;
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\Pdf\PageNumbering;
use App\Utils\VendorHtmlEngine;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Response;
use Turbo124\Beacon\Facades\LightLogs;
class PreviewPurchaseOrderController extends BaseController
{
use MakesHash;
use MakesInvoiceHtml;
use PageNumbering;
public function __construct()
{
parent::__construct();
}
/**
* Returns a template filled with entity variables.
*
* @return \Illuminate\Http\Response
*
* @OA\Post(
* path="/api/v1/preview/purchase_order",
* operationId="getPreviewPurchaseOrder",
* tags={"preview"},
* summary="Returns a pdf preview for purchase order",
* description="Returns a pdf preview for purchase order.",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Response(
* response=200,
* description="The pdf response",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function show()
{
if (request()->has('entity') &&
request()->has('entity_id') &&
! empty(request()->input('entity')) &&
! empty(request()->input('entity_id')) &&
request()->has('body')) {
$design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) {
return response()->json(['message' => ctrans('texts.invalid_design_object')], 400);
}
$entity_obj = PurchaseOrder::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first();
if (! $entity_obj) {
return $this->blankEntity();
}
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($entity_obj->company->locale());
$t->replace(Ninja::transformTranslations($entity_obj->company->settings));
$html = new VendorHtmlEngine($entity_obj->invitations()->first());
$design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name'];
$design_class = new $design_namespace();
$state = [
'template' => $design_class->elements([
'client' => null,
'vendor' => $entity_obj->vendor,
'entity' => $entity_obj,
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
'variables' => $html->generateLabelsAndValues(),
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $entity_obj->company->markdown_enabled,
];
$design = new Design(request()->design['name']);
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
}
//if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
}
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
//else
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
return $this->blankEntity();
}
public function live(PreviewPurchaseOrderRequest $request)
{
$company = auth()->user()->company();
MultiDB::setDb($company->db);
$repo = new PurchaseOrderRepository();
$entity_obj = PurchaseOrderFactory::create($company->id, auth()->user()->id);
$class = PurchaseOrder::class;
try {
DB::connection(config('database.default'))->beginTransaction();
if($request->has('entity_id')){
$entity_obj = $class::on(config('database.default'))
->with('vendor.company')
->where('id', $this->decodePrimaryKey($request->input('entity_id')))
->where('company_id', $company->id)
->withTrashed()
->first();
}
$entity_obj = $repo->save($request->all(), $entity_obj);
if(!$request->has('entity_id'))
$entity_obj->service()->fillDefaults()->save();
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($entity_obj->company->locale());
$t->replace(Ninja::transformTranslations($entity_obj->company->settings));
$html = new VendorHtmlEngine($entity_obj->invitations()->first());
$design = \App\Models\Design::find($entity_obj->design_id);
/* Catch all in case migration doesn't pass back a valid design */
if(!$design)
$design = \App\Models\Design::find(2);
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' => null,
'vendor' => $entity_obj->vendor,
'entity' => $entity_obj,
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
'variables' => $html->generateLabelsAndValues(),
'$product' => $design->design->product,
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $entity_obj->company->markdown_enabled,
];
$maker = new PdfMaker($state);
$maker
->design($template)
->build();
DB::connection(config('database.default'))->rollBack();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
}
}
catch(\Exception $e){
DB::connection(config('database.default'))->rollBack();
return;
}
//if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
}
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), $company);
if(Ninja::isHosted())
{
LightLogs::create(new LivePreview())
->increment()
->queue();
}
$response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf');
return $response;
}
private function blankEntity()
{
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
$invitation = PurchaseOrderInvitation::where('company_id', auth()->user()->company()->id)->orderBy('id', 'desc')->first();
/* If we don't have a valid invitation in the system - create a mock using transactions */
if(!$invitation)
return $this->mockEntity();
$design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) {
return response()->json(['message' => 'Invalid custom design object'], 400);
}
$html = new VendorHtmlEngine($invitation);
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]);
$state = [
'template' => $design->elements([
'client' => null,
'vendor' => $invitation->purchase_order->vendor,
'entity' => $invitation->purchase_order,
'pdf_variables' => (array) $invitation->company->settings->pdf_variables,
'products' => request()->design['design']['product'],
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $invitation->company->markdown_enabled,
];
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
}
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
}
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
$response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf');
return $response;
}
private function mockEntity()
{
DB::connection(auth()->user()->company()->db)->beginTransaction();
$vendor = Vendor::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
]);
$contact = VendorContact::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
'vendor_id' => $vendor->id,
'is_primary' => 1,
'send_email' => true,
]);
$purchase_order = PurchaseOrder::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
'vendor_id' => $vendor->id,
'terms' => 'Sample Terms',
'footer' => 'Sample Footer',
'public_notes' => 'Sample Public Notes',
]);
$invitation = PurchaseOrderInvitation::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
'purchase_order_id' => $purchase_order->id,
'vendor_contact_id' => $contact->id,
]);
$purchase_order->setRelation('invitations', $invitation);
$purchase_order->setRelation('vendor', $vendor);
$purchase_order->setRelation('company', auth()->user()->company());
$purchase_order->load('vendor.company');
$design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) {
return response()->json(['message' => 'Invalid custom design object'], 400);
}
$html = new VendorHtmlEngine($purchase_order->invitations()->first());
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]);
$state = [
'template' => $design->elements([
'client' => null,
'vendor' => $purchase_order->vendor,
'entity' => $purchase_order,
'pdf_variables' => (array) $purchase_order->company->settings->pdf_variables,
'products' => request()->design['design']['product'],
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $purchase_order->company->markdown_enabled,
];
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
DB::connection(auth()->user()->company()->db)->rollBack();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
}
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
}
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
if($numbered_pdf)
$pdf = $numbered_pdf;
return $pdf;
}
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
$response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf');
return $response;
}
}

View File

@ -633,11 +633,23 @@ class PurchaseOrderController extends BaseController
//check query parameter for email_type and set the template else use calculateTemplate
PurchaseOrderEmail::dispatch($purchase_order, $purchase_order->company);
if (! $bulk) {
return response()->json(['message' => 'email sent'], 200);
}
case 'cancel':
if($purchase_order->status_id <= PurchaseOrder::STATUS_SENT)
{
$purchase_order->status_id = PurchaseOrder::STATUS_CANCELLED;
$purchase_order->save();
}
if (! $bulk) {
return $this->listResponse($purchase_order);
}
break;
default:
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
break;

View File

@ -158,7 +158,7 @@ class UserController extends BaseController
*/
public function create(CreateUserRequest $request)
{
$user = UserFactory::create(auth()->user()->account->id);
$user = UserFactory::create(auth()->user()->account_id);
return $this->itemResponse($user);
}
@ -396,7 +396,7 @@ class UserController extends BaseController
UserEmailChanged::dispatch($new_user, json_decode($old_user), auth()->user()->company());
}
$user->company_users()->update(["permissions_updated_at" => now()]);
// $user->company_users()->update(["permissions_updated_at" => now()]);
event(new UserWasUpdated($user, auth()->user(), auth()->user()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));

View File

@ -17,11 +17,13 @@ use App\Events\Misc\InvitationWasViewed;
use App\Events\Quote\QuoteWasViewed;
use App\Http\Controllers\Controller;
use App\Jobs\Entity\CreateRawPdf;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation;
use App\Models\Payment;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Models\QuoteInvitation;
use App\Services\ClientPortal\InstantPayment;
@ -95,50 +97,32 @@ class InvitationController extends Controller
}
public function download(string $invitation_key)
{
$invitation = PurchaseOrderInvitation::withTrashed()
->where('key', $invitation_key)
->with('contact.vendor')
->firstOrFail();
if(!$invitation)
return response()->json(["message" => "no record found"], 400);
// public function routerForDownload(string $entity, string $invitation_key)
// {
$file_name = $invitation->purchase_order->numberFormatter().'.pdf';
// set_time_limit(45);
// $file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db);
// if(Ninja::isHosted())
// return $this->returnRawPdf($entity, $invitation_key);
$file = (new CreatePurchaseOrderPdf($invitation))->rawPdf();
// return redirect('client/'.$entity.'/'.$invitation_key.'/download_pdf');
// }
$headers = ['Content-Type' => 'application/pdf'];
// private function returnRawPdf(string $entity, string $invitation_key)
// {
if(request()->input('inline') == 'true')
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
// if(!in_array($entity, ['invoice', 'credit', 'quote', 'recurring_invoice']))
// return response()->json(['message' => 'Invalid resource request']);
return response()->streamDownload(function () use($file) {
echo $file;
}, $file_name, $headers);
}
// $key = $entity.'_id';
// $entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
// $invitation = $entity_obj::where('key', $invitation_key)
// ->with('contact.client')
// ->firstOrFail();
// if(!$invitation)
// return response()->json(["message" => "no record found"], 400);
// $file_name = $invitation->purchase_order->numberFormatter().'.pdf';
// $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);
// }

View File

@ -63,35 +63,57 @@ class PasswordProtection
//user is attempting to reauth with OAuth - check the token value
//todo expand this to include all OAuth providers
$user = false;
$google = new Google();
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
if (is_array($user)) {
$query = [
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google'
];
if(auth()->user()->oauth_provider_id == 'google')
{
$user = false;
$google = new Google();
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
//If OAuth and user also has a password set - check both
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $x_api_password)) {
if (is_array($user)) {
$query = [
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google'
];
nlog("existing user with password");
//If OAuth and user also has a password set - check both
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $x_api_password)) {
nlog("existing user with password");
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
}
elseif($existing_user = MultiDB::hasUser($query) && !auth()->user()->company()->oauth_password_required){
nlog("existing user without password");
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
}
}
}
elseif(auth()->user()->oauth_provider_id == 'microsoft')
{
try{
$payload = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', request()->header('X-API-OAUTH-PASSWORD'))[1]))));
}
catch(\Exception $e){
nlog("could not decode microsoft response");
return response()->json(['message' => 'Could not decode the response from Microsoft'], 412);
}
if($payload->preferred_username == auth()->user()->email){
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
}
elseif($existing_user = MultiDB::hasUser($query) && !auth()->user()->company()->oauth_password_required){
nlog("existing user without password");
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
}
}
return response()->json($error, 412);

View File

@ -18,7 +18,7 @@ class ShowRecurringInvoiceRequest extends Request
{
public function authorize() : bool
{
return auth()->guard('contact')->user()->client->id === $this->recurring_invoice->client_id
return auth()->guard('contact')->user()->client->id == $this->recurring_invoice->client_id
&& auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_RECURRING_INVOICES;
}

View File

@ -73,7 +73,9 @@ class UpdatePaymentRequest extends Request
if (isset($input['invoices']) && is_array($input['invoices']) !== false) {
foreach ($input['invoices'] as $key => $value) {
$input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']);
if(array_key_exists('invoice_id', $input['invoices'][$key]))
$input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']);
}
}
$this->replace($input);

View File

@ -0,0 +1,62 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Preview;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Project\ValidProjectForClient;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\PurchaseOrder;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class PreviewPurchaseOrderRequest extends Request
{
use MakesHash;
use CleanLineItems;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('create', PurchaseOrder::class);
}
public function rules()
{
$rules = [];
$rules['number'] = ['nullable'];
return $rules;
}
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = 0;
$input['balance'] = 0;
$input['number'] = ctrans('texts.live_preview') . " #". rand(0,1000);
$this->replace($input);
}
}

View File

@ -58,6 +58,6 @@ class StoreProjectRequest extends Request
public function getClient($client_id)
{
return Client::find($client_id);
return Client::withTrashed()->find($client_id);
}
}

View File

@ -14,12 +14,15 @@ namespace App\Http\Requests\PurchaseOrder;
use App\Http\Requests\Request;
use App\Models\PurchaseOrder;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class StorePurchaseOrderRequest extends Request
{
use MakesHash;
use CleanLineItems;
/**
* Determine if the user is authorized to make this request.
*
@ -43,8 +46,6 @@ class StorePurchaseOrderRequest extends Request
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)];
$rules['discount'] = 'sometimes|numeric';
$rules['is_amount_discount'] = ['boolean'];
$rules['line_items'] = 'array';
return $rules;
@ -56,6 +57,12 @@ class StorePurchaseOrderRequest extends Request
$input = $this->decodePrimaryKeys($input);
if (isset($input['line_items']) && is_array($input['line_items']))
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = 0;
$input['balance'] = 0;
$this->replace($input);
}

View File

@ -14,6 +14,7 @@ namespace App\Http\Requests\PurchaseOrder;
use App\Http\Requests\Request;
use App\Utils\Traits\ChecksEntityStatus;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
@ -21,6 +22,7 @@ class UpdatePurchaseOrderRequest extends Request
{
use ChecksEntityStatus;
use MakesHash;
use CleanLineItems;
/**
* Determine if the user is authorized to make this request.
@ -57,6 +59,10 @@ class UpdatePurchaseOrderRequest extends Request
$input['id'] = $this->purchase_order->id;
if (isset($input['line_items']) && is_array($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
}
$this->replace($input);
}
}

View File

@ -1423,21 +1423,21 @@ class CompanyImport implements ShouldQueue
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextInvoiceNumber($client = Client::find($obj_array['client_id']),$new_obj);
$new_obj->number = $this->getNextInvoiceNumber($client = Client::withTrashed()->find($obj_array['client_id']),$new_obj);
}
elseif($class == 'App\Models\Payment' && is_null($obj->{$match_key})){
$new_obj = new Payment();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextPaymentNumber($client = Client::find($obj_array['client_id']), $new_obj);
$new_obj->number = $this->getNextPaymentNumber($client = Client::withTrashed()->find($obj_array['client_id']), $new_obj);
}
elseif($class == 'App\Models\Quote' && is_null($obj->{$match_key})){
$new_obj = new Quote();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextQuoteNumber($client = Client::find($obj_array['client_id']), $new_obj);
$new_obj->number = $this->getNextQuoteNumber($client = Client::withTrashed()->find($obj_array['client_id']), $new_obj);
}
elseif($class == 'App\Models\ClientContact'){
$new_obj = new ClientContact();

View File

@ -200,6 +200,14 @@ class NinjaMailerJob implements ShouldQueue
$user = User::find($this->decodePrimaryKey($sending_user));
/* Always ensure the user is set on the correct account */
if($user->account_id != $this->company->account_id){
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
nlog("Sending via {$user->name()}");
$token = $this->refreshOfficeToken($user);
@ -236,6 +244,14 @@ class NinjaMailerJob implements ShouldQueue
$user = User::find($this->decodePrimaryKey($sending_user));
/* Always ensure the user is set on the correct account */
if($user->account_id != $this->company->account_id){
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
nlog("Sending via {$user->name()}");
$google = (new Google())->init();
@ -292,8 +308,9 @@ class NinjaMailerJob implements ShouldQueue
private function preFlightChecksFail()
{
/* If we are migrating data we don't want to fire any emails */
if ($this->nmo->company->is_disabled && !$this->override)
if($this->company->is_disabled && !$this->override)
return true;
/* On the hosted platform we set default contacts a @example.com email address - we shouldn't send emails to these types of addresses */
@ -301,17 +318,25 @@ class NinjaMailerJob implements ShouldQueue
return true;
/* GMail users are uncapped */
if(Ninja::isHosted() && $this->nmo->settings->email_sending_method == 'gmail')
if(Ninja::isHosted() && ($this->nmo->settings->email_sending_method == 'gmail' || $this->nmo->settings->email_sending_method == 'office365'))
return false;
/* On the hosted platform, if the user is over the email quotas, we do not send the email. */
if(Ninja::isHosted() && $this->company->account && $this->company->account->emailQuotaExceeded())
return true;
/* To handle spam users we drop all emails from flagged accounts */
if(Ninja::isHosted() && $this->company->account && $this->company->account->is_flagged)
return true;
/* Ensure the user has a valid email address */
if(!str_contains($this->nmo->to_user->email, "@"))
return true;
/* On the hosted platform we actively scan all outbound emails to ensure outbound email quality remains high */
if(class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class))
return (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run();
return false;
}
@ -342,23 +367,48 @@ class NinjaMailerJob implements ShouldQueue
private function refreshOfficeToken($user)
{
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
$expiry = $user->oauth_user_token_expiry ?: now()->subDay();
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => config('ninja.o365.client_id') ,
'client_secret' => config('ninja.o365.client_secret') ,
'scope' => 'email Mail.ReadWrite Mail.Send offline_access profile User.Read openid',
'grant_type' => 'refresh_token',
'refresh_token' => $user->oauth_user_refresh_token
],
])->getBody()->getContents());
if($expiry->lt(now()))
{
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
if($token)
return $token->access_token;
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => config('ninja.o365.client_id') ,
'client_secret' => config('ninja.o365.client_secret') ,
'scope' => 'email Mail.Send offline_access profile User.Read openid',
'grant_type' => 'refresh_token',
'refresh_token' => $user->oauth_user_refresh_token
],
])->getBody()->getContents());
return false;
nlog($token);
if($token){
$user->oauth_user_refresh_token = property_exists($token, 'refresh_token') ? $token->refresh_token : $user->oauth_user_refresh_token;
$user->oauth_user_token = $token->access_token;
$user->oauth_user_token_expiry = now()->addSeconds($token->expires_in);
$user->save();
return $token->access_token;
}
return false;
}
return $user->oauth_user_refresh_token;
}
/**
* Is this the cleanest way to requeue a job?
*
* $this->delete();
*
* $job = NinjaMailerJob::dispatch($this->nmo, $this->override)->delay(3600);
*/
}

View File

@ -923,6 +923,9 @@ class Import implements ShouldQueue
$modified['company_id'] = $this->company->id;
$modified['line_items'] = $this->cleanItems($modified['line_items']);
if(array_key_exists('next_send_date', $resource))
$modified['next_send_date_client'] = $resource['next_send_date'];
if(array_key_exists('created_at', $modified))
$modified['created_at'] = Carbon::parse($modified['created_at']);

View File

@ -65,6 +65,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue
public $vendor;
private string $path = '';
private string $file_path = '';
/**
* Create a new job instance.
*
@ -88,6 +92,32 @@ class CreatePurchaseOrderPdf implements ShouldQueue
}
public function handle()
{
$pdf = $this->rawPdf();
if ($pdf) {
try{
if(!Storage::disk($this->disk)->exists($this->path))
Storage::disk($this->disk)->makeDirectory($this->path, 0775);
Storage::disk($this->disk)->put($this->file_path, $pdf, 'public');
}
catch(\Exception $e)
{
throw new FilePermissionsFailure($e->getMessage());
}
}
return $this->file_path;
}
public function rawPdf()
{
MultiDB::setDb($this->company->db);
@ -109,10 +139,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue
$entity_design_id = '';
$path = $this->vendor->purchase_order_filepath($this->invitation);
$this->path = $this->vendor->purchase_order_filepath($this->invitation);
$entity_design_id = 'purchase_order_design_id';
$file_path = $path.$this->entity->numberFormatter().'.pdf';
$this->file_path = $this->path.$this->entity->numberFormatter().'.pdf';
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn');
@ -191,25 +221,8 @@ class CreatePurchaseOrderPdf implements ShouldQueue
info($maker->getCompiledHTML());
}
if ($pdf) {
return $pdf;
try{
if(!Storage::disk($this->disk)->exists($path))
Storage::disk($this->disk)->makeDirectory($path, 0775);
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
}
catch(\Exception $e)
{
throw new FilePermissionsFailure($e->getMessage());
}
}
return $file_path;
}
public function failed($e)

View File

@ -33,7 +33,7 @@ class Account extends BaseModel
use PresentableTrait;
use MakesHash;
private $free_plan_email_quota = 250;
private $free_plan_email_quota = 100;
private $paid_plan_email_quota = 500;
/**
@ -373,10 +373,15 @@ class Account extends BaseModel
public function getDailyEmailLimit()
{
if($this->is_flagged)
return 0;
if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0)
return 20;
if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() <= 2 && !$this->payment_id)
return 20;
if($this->isPaid()){
$limit = $this->paid_plan_email_quota;
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 100;

View File

@ -63,6 +63,8 @@ class Gateway extends StaticModel
$link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99';
} elseif ($this->id == 20 || $this->id == 56) {
$link = 'https://dashboard.stripe.com/account/apikeys';
} elseif ($this->id == 59) {
$link = 'https://www.forte.net/';
}
return $link;
@ -170,6 +172,12 @@ class Gateway extends StaticModel
GatewayType::HOSTED_PAGE => ['refund' => false, 'token_billing' => false, 'webhooks' => [' ']] // Razorpay
];
break;
case 59:
return [
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], // Forte
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']],
];
break;
default:
return [];
break;

View File

@ -126,6 +126,26 @@ class CompanyPresenter extends EntityPresenter
}
}
public function address1()
{
return $this->entity->settings->address1;
}
public function address2()
{
return $this->entity->settings->address2;
}
public function qr_iban()
{
return $this->entity->getSetting('qr_iban');
}
public function besr_id()
{
return $this->entity->getSetting('besr_id');
}
public function getSpcQrCode($client_currency, $invoice_number, $balance_due_raw, $user_iban)
{
$settings = $this->entity->settings;

View File

@ -34,7 +34,6 @@ class PurchaseOrder extends BaseModel
'discount',
'company_id',
'status_id',
'user_id',
'last_sent_date',
'is_deleted',
'po_number',
@ -84,7 +83,7 @@ class PurchaseOrder extends BaseModel
'balance',
'partial',
'paid_to_date',
'subscription_id',
// 'subscription_id',
'vendor_id',
'last_viewed'
];
@ -154,7 +153,7 @@ class PurchaseOrder extends BaseModel
public function vendor()
{
return $this->belongsTo(Vendor::class);
return $this->belongsTo(Vendor::class)->withTrashed();
}
public function history()

View File

@ -106,6 +106,7 @@ class User extends Authenticatable implements MustVerifyEmail
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
'oauth_user_token_expiry' => 'datetime',
];

View File

@ -0,0 +1,93 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Notifications\Ninja;
use App\Models\Account;
use App\Models\Client;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class NewAccountNotification extends Notification
{
/**
* Create a new notification instance.
*
* @return void
*/
protected Account $account;
protected Client $client;
public function __construct(Account $account, Client $client)
{
$this->account = $account;
$this->client = $client;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['slack'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
public function toSlack($notifiable)
{
$content = "New Trial Started\n";
$content = "{$this->client->name}\n";
$content = "Account key: {$this->account->key}\n";
$content = "Users: {$this->account->users()->pluck('email')}\n";
$content = "Contacts: {$this->client->contacts()->pluck('email')}\n";
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content($content);
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Notifications\Ninja;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SpamNotification extends Notification
{
/**
* Create a new notification instance.
*
* @return void
*/
protected array $spam_list;
public function __construct($spam_list)
{
$this->spam_list = $spam_list;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['slack'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
public function toSlack($notifiable)
{
$content = '';
foreach($this->spam_list as $spam_list)
{
if(array_key_exists('companies', $spam_list))
{
$content .= " Companies \n";
foreach($spam_list['companies'] as $company)
{
$content .= "{$company['name']} - c_key={$company['company_key']} - a_key={$company['account_key']} - {$company['owner']} \n";
}
}
if(array_key_exists('templates', $spam_list))
{
$content .= " Templates \n";
foreach($spam_list['templates'] as $company)
{
$content .= "{$company['name']} - c_key={$company['company_key']} - a_key={$company['account_key']} - {$company['owner']} \n";
}
}
if(array_key_exists('users', $spam_list))
{
$content .= ' Users \n';
foreach($spam_list['users'] as $user)
{
$content .= "{$user['email']} - a_key={$user['account_key']} - created={$user['created']} \n";
}
}
}
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content($content);
}
}

View File

@ -0,0 +1,150 @@
<?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\Forte;
use App\Models\Payment;
use App\Models\GatewayType;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Validator;
use App\PaymentDrivers\FortePaymentDriver;
class ACH
{
use MakesHash;
public $forte;
private $forte_base_uri="";
private $forte_api_access_id="";
private $forte_secure_key="";
private $forte_auth_organization_id="";
private $forte_organization_id="";
private $forte_location_id="";
public function __construct(FortePaymentDriver $forte)
{
$this->forte = $forte;
$this->forte_base_uri = "https://sandbox.forte.net/api/v3/";
if($this->forte->company_gateway->getConfigField('testMode') == false){
$this->forte_base_uri = "https://api.forte.net/v3/";
}
$this->forte_api_access_id = $this->forte->company_gateway->getConfigField('apiAccessId');
$this->forte_secure_key = $this->forte->company_gateway->getConfigField('secureKey');
$this->forte_auth_organization_id = $this->forte->company_gateway->getConfigField('authOrganizationId');
$this->forte_organization_id = $this->forte->company_gateway->getConfigField('organizationId');
$this->forte_location_id = $this->forte->company_gateway->getConfigField('locationId');
}
public function authorizeView(array $data)
{
return render('gateways.forte.ach.authorize', $data);
}
public function authorizeResponse(Request $request)
{
$payment_meta = new \stdClass;
$payment_meta->brand = (string)ctrans('texts.ach');
$payment_meta->last4 = (string) $request->last_4;
$payment_meta->exp_year = '-';
$payment_meta->type = GatewayType::BANK_TRANSFER;
$data = [
'payment_meta' => $payment_meta,
'token' => $request->one_time_token,
'payment_method_id' => $request->gateway_type_id,
];
$this->forte->storeGatewayToken($data);
return redirect()->route('client.payment_methods.index')->withSuccess('Payment Method added.');
}
public function paymentView(array $data)
{
$this->forte->payment_hash->data = array_merge((array) $this->forte->payment_hash->data, $data);
$this->forte->payment_hash->save();
$data['gateway'] = $this;
return render('gateways.forte.ach.pay', $data);
}
public function paymentResponse($request)
{
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->firstOrFail();
try {
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $this->forte_base_uri.'organizations/'.$this->forte_organization_id.'/locations/'.$this->forte_location_id.'/transactions',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS =>'{
"action":"sale",
"authorization_amount": '.$payment_hash->data->total->amount_with_fee.',
"echeck":{
"sec_code":"PPD",
},
"billing_address":{
"first_name": "'.$this->forte->client->name.'",
"last_name": "'.$this->forte->client->name.'"
},
"echeck":{
"one_time_token":"'.$request->payment_token.'"
}
}',
CURLOPT_HTTPHEADER => array(
'X-Forte-Auth-Organization-Id: '.$this->forte_organization_id,
'Content-Type: application/json',
'Authorization: Basic '.base64_encode($this->forte_api_access_id.':'.$this->forte_secure_key),
'Cookie: visid_incap_621087=u18+3REYR/iISgzZxOF5s2ODW2IAAAAAQUIPAAAAAADuGqKgECQLS81FcSDrmhGe; nlbi_621087=YHngadhJ2VU+yr7/R1efXgAAAAD3mQyhqmnLls8PRu4iN58G; incap_ses_1136_621087=CVdrXUdhIlm9WJNDieLDD4QVXGIAAAAAvBwvkUcwhM0+OwvdPm2stg=='
),
));
$response = curl_exec($curl);
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$response=json_decode($response);
} catch (\Throwable $th) {
throw $th;
}
if ($httpcode>299) {
$error = Validator::make([], []);
$error->getMessageBag()->add('gateway_error', $response->response->response_desc);
return redirect('client/invoices')->withErrors($error);
}
$data = [
'payment_method' => $request->payment_method_id,
'payment_type' => PaymentType::ACH,
'amount' => $payment_hash->data->amount_with_fee,
'transaction_reference' => $response->transaction_id,
'gateway_type_id' => GatewayType::BANK_TRANSFER,
];
$payment=$this->forte->createPayment($data, Payment::STATUS_COMPLETED);
return redirect('client/invoices')->withSuccess('Invoice paid.');
}
}

View File

@ -0,0 +1,160 @@
<?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\Forte;
use App\Models\Payment;
use App\Models\GatewayType;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use App\PaymentDrivers\FortePaymentDriver;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
class CreditCard
{
use MakesHash;
public $forte;
private $forte_base_uri="";
private $forte_api_access_id="";
private $forte_secure_key="";
private $forte_auth_organization_id="";
private $forte_organization_id="";
private $forte_location_id="";
public function __construct(FortePaymentDriver $forte)
{
$this->forte = $forte;
$this->forte_base_uri = "https://sandbox.forte.net/api/v3/";
if($this->forte->company_gateway->getConfigField('testMode') == false){
$this->forte_base_uri = "https://api.forte.net/v3/";
}
$this->forte_api_access_id = $this->forte->company_gateway->getConfigField('apiAccessId');
$this->forte_secure_key = $this->forte->company_gateway->getConfigField('secureKey');
$this->forte_auth_organization_id = $this->forte->company_gateway->getConfigField('authOrganizationId');
$this->forte_organization_id = $this->forte->company_gateway->getConfigField('organizationId');
$this->forte_location_id = $this->forte->company_gateway->getConfigField('locationId');
}
public function authorizeView(array $data)
{
return render('gateways.forte.credit_card.authorize', $data);
}
public function authorizeResponse($request)
{
$payment_meta = new \stdClass;
$payment_meta->exp_month = (string) $request->expire_month;
$payment_meta->exp_year = (string) $request->expire_year;
$payment_meta->brand = (string) $request->card_type;
$payment_meta->last4 = (string) $request->last_4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$data = [
'payment_meta' => $payment_meta,
'token' => $request->one_time_token,
'payment_method_id' => $request->payment_method_id,
];
$this->forte->storeGatewayToken($data);
return redirect()->route('client.payment_methods.index')->withSuccess('Payment Method added.');
}
public function paymentView(array $data)
{
$this->forte->payment_hash->data = array_merge((array) $this->forte->payment_hash->data, $data);
$this->forte->payment_hash->save();
$data['gateway'] = $this;
return render('gateways.forte.credit_card.pay', $data);
}
public function paymentResponse(PaymentResponseRequest $request)
{
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->firstOrFail();
$amount_with_fee = $payment_hash->data->total->amount_with_fee;
$invoice_totals = $payment_hash->data->total->invoice_totals;
$fee_total = 0;
for ($i = ($invoice_totals * 100) ; $i < ($amount_with_fee * 100); $i++) {
$calculated_fee = ( 3 * $i) / 100;
$calculated_amount_with_fee = round(($i + $calculated_fee) / 100,2);
if ($calculated_amount_with_fee == $amount_with_fee) {
$fee_total = round($calculated_fee / 100,2);
$amount_with_fee = $calculated_amount_with_fee;
break;
}
}
try {
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => $this->forte_base_uri.'organizations/'.$this->forte_organization_id.'/locations/'.$this->forte_location_id.'/transactions',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS =>'{
"action":"sale",
"authorization_amount":'.$amount_with_fee.',
"service_fee_amount":'.$fee_total.',
"billing_address":{
"first_name":"'.$this->forte->client->name.'",
"last_name":"'.$this->forte->client->name.'"
},
"card":{
"one_time_token":"'.$request->payment_token.'"
}
}',
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
'X-Forte-Auth-Organization-Id: '.$this->forte_organization_id,
'Authorization: Basic '.base64_encode($this->forte_api_access_id.':'.$this->forte_secure_key)
),
));
$response = curl_exec($curl);
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$response=json_decode($response);
} catch (\Throwable $th) {
throw $th;
}
if ($httpcode>299) {
$error = Validator::make([], []);
$error->getMessageBag()->add('gateway_error', $response->response->response_desc);
return redirect('client/invoices')->withErrors($error);
}
$data = [
'payment_method' => $request->payment_method_id,
'payment_type' => PaymentType::parseCardType(strtolower($request->card_brand)) ?: PaymentType::CREDIT_CARD_OTHER,
'amount' => $payment_hash->data->amount_with_fee,
'transaction_reference' => $response->transaction_id,
'gateway_type_id' => GatewayType::CREDIT_CARD,
];
$payment=$this->forte->createPayment($data, Payment::STATUS_COMPLETED);
return redirect('client/invoices')->withSuccess('Invoice paid.');
}
}

View File

@ -0,0 +1,90 @@
<?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://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
use App\Models\SystemLog;
use App\Models\GatewayType;
use App\Utils\Traits\MakesHash;
use App\PaymentDrivers\Forte\ACH;
use App\PaymentDrivers\Forte\CreditCard;
class FortePaymentDriver extends BaseDriver
{
use MakesHash;
public $refundable = true; //does this gateway support refunds?
public $token_billing = true; //does this gateway support token billing?
public $can_authorise_credit_card = true; //does this gateway support authorizations?
public $gateway; //initialized gateway
public $payment_method; //initialized payment method
public static $methods = [
GatewayType::CREDIT_CARD => CreditCard::class,
GatewayType::BANK_TRANSFER => ACH::class,
];
/**
* Returns the gateway types.
*/
public function gatewayTypes(): array
{
$types = [];
$types[] = GatewayType::CREDIT_CARD;
$types[] = GatewayType::BANK_TRANSFER;
return $types;
}
const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
public function setPaymentMethod($payment_method_id)
{
$class = self::$methods[$payment_method_id];
$this->payment_method = new $class($this);
return $this;
}
public function authorizeView(array $data)
{
return $this->payment_method->authorizeView($data); //this is your custom implementation from here
}
public function authorizeResponse($request)
{
return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here
}
public function processPaymentView(array $data)
{
return $this->payment_method->paymentView($data); //this is your custom implementation from here
}
public function processPaymentResponse($request)
{
return $this->payment_method->paymentResponse($request); //this is your custom implementation from here
}
// public function refund(Payment $payment, $amount, $return_client_response = false)
// {
// return $this->payment_method->yourRefundImplementationHere(); //this is your custom implementation from here
// }
// public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
// {
// return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here
// }
}

View File

@ -121,7 +121,19 @@ class PaymentIntentWebhook implements ShouldQueue
nlog("payment intent");
nlog($this->stripe_request);
if(optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['allowed_source_types']))
if(array_key_exists('allowed_source_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['allowed_source_types']))
{
nlog("hash found");
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client);
}
elseif(array_key_exists('payment_method_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['payment_method_types']))
{
nlog("hash found");

View File

@ -220,6 +220,7 @@ class Statement
protected function getInvoices(): \Illuminate\Support\LazyCollection
{
return Invoice::withTrashed()
->with('payments.type')
->where('is_deleted', false)
->where('company_id', $this->client->company_id)
->where('client_id', $this->client->id)

View File

@ -255,6 +255,7 @@ class InstantPayment
'tokens' => $tokens,
'payment_method_id' => $payment_method_id,
'amount_with_fee' => $invoice_totals + $fee_totals,
'client' => $client,
];
if ($is_credit_payment || $totals <= 0) {

View File

@ -60,6 +60,8 @@ class Design extends BaseDesign
public $company;
public $client_or_vendor_entity;
/** @var array */
public $aging = [];
@ -173,9 +175,27 @@ class Design extends BaseDesign
$this->sharedFooterElements(),
],
],
// 'swiss-qr' => [
// 'id' => 'swiss-qr',
// 'elements' => $this->swissQrCodeElement(),
// ]
];
}
public function swissQrCodeElement() :array
{
if($this->type == self::DELIVERY_NOTE)
return [];
$elements = [];
if(strlen($this->company->getSetting('qr_iban')) > 5 && strlen($this->company->getSetting('besr_id')) > 1)
$elements[] = ['element' => 'qr_code', 'content' => '$swiss_qr', 'show_empty' => false, 'properties' => ['data-ref' => 'swiss-qr-code']];
return $elements;
}
public function companyDetails(): array
{
$variables = $this->context['pdf_variables']['company_details'];
@ -312,7 +332,10 @@ class Design extends BaseDesign
$_variable = explode('.', $variable)[1];
$_customs = ['custom1', 'custom2', 'custom3', 'custom4'];
if (in_array($_variable, $_customs)) {
/* 2/7/2022 don't show custom values if they are empty */
$var = str_replace("custom", "custom_value", $_variable);
if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) {
$elements[] = ['element' => 'tr', 'elements' => [
['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']],
['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]],
@ -785,7 +808,7 @@ class Design extends BaseDesign
foreach ($taxes as $i => $tax) {
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i . '-label']],
['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->context['client']), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]],
['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->client_or_vendor_entity), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]],
]];
}
} elseif ($variable == '$line_taxes') {
@ -798,13 +821,13 @@ class Design extends BaseDesign
foreach ($taxes as $i => $tax) {
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i . '-label']],
['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->context['client']), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]],
['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->client_or_vendor_entity), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]],
]];
}
} elseif (Str::startsWith($variable, '$custom_surcharge')) {
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
$visible = (int)$this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0' || !$this->entity->{$_variable};
$visible = (int)$this->entity->{$_variable} > 0 || (int)$this->entity->{$_variable} < 0 || !$this->entity->{$_variable};
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],

View File

@ -32,10 +32,12 @@ trait DesignHelpers
if (isset($this->context['vendor'])) {
$this->vendor = $this->context['vendor'];
$this->client_or_vendor_entity = $this->context['vendor'];
}
if (isset($this->context['client'])) {
$this->client = $this->context['client'];
$this->client_or_vendor_entity = $this->context['client'];
}
if (isset($this->context['entity'])) {

View File

@ -41,7 +41,20 @@ class ApplyNumber extends AbstractService
return $this->purchase_order;
}
$this->trySaving();
switch ($this->vendor->company->getSetting('counter_number_applied')) {
case 'when_saved':
$this->trySaving();
break;
case 'when_sent':
if ($this->invoice->status_id == PurchaseOrder::STATUS_SENT) {
$this->trySaving();
}
break;
default:
break;
}
return $this->purchase_order;
}

View File

@ -51,7 +51,6 @@ class PurchaseOrderService
// //TODO implement design, footer, terms
// /* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
// if (!isset($this->purchase_order->exchange_rate) && $this->purchase_order->client->currency()->id != (int)$this->purchase_order->company->settings->currency_id)
// $this->purchase_order->exchange_rate = $this->purchase_order->client->currency()->exchange_rate;
@ -89,7 +88,6 @@ class PurchaseOrderService
return $this;
}
public function touchPdf($force = false)
{
try {

View File

@ -89,7 +89,7 @@ class PurchaseOrderTransformer extends EntityTransformer
'custom_surcharge_tax3' => (bool)$purchase_order->custom_surcharge_tax3,
'custom_surcharge_tax4' => (bool)$purchase_order->custom_surcharge_tax4,
'line_items' => $purchase_order->line_items ?: (array)[],
'entity_type' => 'purchase_order',
'entity_type' => 'purchaseOrder',
'exchange_rate' => (float)$purchase_order->exchange_rate,
'paid_to_date' => (float)$purchase_order->paid_to_date,
'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id),

View File

@ -12,6 +12,7 @@
namespace App\Utils;
use App\Helpers\SwissQr\SwissQrGenerator;
use App\Models\Country;
use App\Models\CreditInvitation;
use App\Models\GatewayType;
@ -162,6 +163,17 @@ class HtmlEngine
if($this->entity->vendor) {
$data['$invoice.vendor'] = ['value' => $this->entity->vendor->present()->name(), 'label' => ctrans('texts.vendor_name')];
}
if(strlen($this->company->getSetting('qr_iban')) > 5 && strlen($this->company->getSetting('besr_id')) > 1)
{
try{
$data['$swiss_qr'] = ['value' => (new SwissQrGenerator($this->entity, $this->company))->run(), 'label' => ''];
}
catch(\Exception $e){
$data['$swiss_qr'] = ['value' => '', 'label' => ''];
}
}
}
if ($this->entity_string == 'quote') {
@ -281,6 +293,7 @@ class HtmlEngine
$data['$assigned_to_user'] = ['value' => $this->entity->assigned_user ? $this->entity->assigned_user->present()->name() : '', 'label' => ctrans('texts.name')];
$data['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')];
$data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];

View File

@ -16,16 +16,22 @@ use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\MakesTemplateData;
use App\Utils\VendorHtmlEngine;
use DB;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Str;
use League\CommonMark\CommonMarkConverter;
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
@ -78,6 +84,10 @@ class TemplateEngine
public function build()
{
if ($this->template == 'email_template_null')
$this->template = 'email_template_purchase_order';
return $this->setEntity()
->setSettingsObject()
->setTemplates()
@ -88,7 +98,7 @@ class TemplateEngine
private function setEntity()
{
if (strlen($this->entity) > 1 && strlen($this->entity_id) > 1) {
$class = 'App\Models\\'.ucfirst($this->entity);
$class = 'App\Models\\'.ucfirst(Str::camel($this->entity));
$this->entity_obj = $class::withTrashed()->where('id', $this->decodePrimaryKey($this->entity_id))->company()->first();
} else {
$this->mockEntity();
@ -99,7 +109,11 @@ class TemplateEngine
private function setSettingsObject()
{
if ($this->entity_obj) {
if($this->entity == 'purchaseOrder'){
$this->settings_entity = auth()->user()->company();
$this->settings = $this->settings_entity->settings;
}
elseif ($this->entity_obj->client()->exists()) {
$this->settings_entity = $this->entity_obj->client;
$this->settings = $this->settings_entity->getMergedSettings();
} else {
@ -143,9 +157,13 @@ class TemplateEngine
$this->raw_body = $this->body;
$this->raw_subject = $this->subject;
if ($this->entity_obj) {
if($this->entity == 'purchaseOrder'){
$this->fakerValues();
}
elseif ($this->entity_obj->client()->exists()) {
$this->entityValues($this->entity_obj->client->primary_contact()->first());
} else {
}
else {
$this->fakerValues();
}
@ -172,11 +190,13 @@ class TemplateEngine
private function entityValues($contact)
{
$this->labels_and_values = (new HtmlEngine($this->entity_obj->invitations->first()))->generateLabelsAndValues();
if($this->entity == 'purchaseOrder')
$this->labels_and_values = (new VendorHtmlEngine($this->entity_obj->invitations->first()))->generateLabelsAndValues();
else
$this->labels_and_values = (new HtmlEngine($this->entity_obj->invitations->first()))->generateLabelsAndValues();
$this->body = strtr($this->body, $this->labels_and_values['labels']);
$this->body = strtr($this->body, $this->labels_and_values['values']);
// $this->body = str_replace("\n", "<br>", $this->body);
$this->subject = strtr($this->subject, $this->labels_and_values['labels']);
$this->subject = strtr($this->subject, $this->labels_and_values['values']);
@ -198,7 +218,18 @@ class TemplateEngine
$data['footer'] = '';
$data['logo'] = auth()->user()->company()->present()->logo();
$data = array_merge($data, Helpers::sharedEmailVariables($this->entity_obj->client));
if($this->entity_obj->client()->exists())
$data = array_merge($data, Helpers::sharedEmailVariables($this->entity_obj->client));
else{
$data['signature'] = $this->settings->email_signature;
$data['settings'] = $this->settings;
$data['whitelabel'] = $this->entity_obj ? $this->entity_obj->company->account->isPaid() : true;
$data['company'] = $this->entity_obj ? $this->entity_obj->company : '';
$data['settings'] = $this->settings;
}
if ($email_style == 'custom') {
$wrapper = $this->settings_entity->getSetting('email_style_custom');
@ -241,8 +272,13 @@ class TemplateEngine
private function mockEntity()
{
if(!$this->entity && $this->template && str_contains($this->template, 'purchase_order'))
$this->entity = 'purchaseOrder';
DB::connection(config('database.default'))->beginTransaction();
$vendor = false;
$client = Client::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
@ -289,12 +325,60 @@ class TemplateEngine
]);
}
$this->entity_obj->setRelation('invitations', $invitation);
$this->entity_obj->setRelation('client', $client);
$this->entity_obj->setRelation('company', auth()->user()->company());
$this->entity_obj->load('client');
$client->setRelation('company', auth()->user()->company());
$client->load('company');
if($this->entity == 'purchaseOrder')
{
$vendor = Vendor::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
]);
$contact = VendorContact::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
'vendor_id' => $vendor->id,
'is_primary' => 1,
'send_email' => true,
]);
$this->entity_obj = PurchaseOrder::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
'vendor_id' => $vendor->id,
]);
$invitation = PurchaseOrderInvitation::factory()->create([
'user_id' => auth()->user()->id,
'company_id' => auth()->user()->company()->id,
'purchase_order_id' => $this->entity_obj->id,
'vendor_contact_id' => $contact->id,
]);
}
if($vendor)
{
$this->entity_obj->setRelation('invitations', $invitation);
$this->entity_obj->setRelation('vendor', $vendor);
$this->entity_obj->setRelation('company', auth()->user()->company());
$this->entity_obj->load('vendor');
$vendor->setRelation('company', auth()->user()->company());
$vendor->load('company');
}
else
{
$this->entity_obj->setRelation('invitations', $invitation);
$this->entity_obj->setRelation('client', $client);
$this->entity_obj->setRelation('company', auth()->user()->company());
$this->entity_obj->load('client');
$client->setRelation('company', auth()->user()->company());
$client->load('company');
}
}
private function tearDown()

View File

@ -111,6 +111,10 @@ trait AppSetup
'subject' => EmailTemplateDefaults::emailCreditSubject(),
'body' => EmailTemplateDefaults::emailCreditTemplate(),
],
'purchase_order' => [
'subject' => EmailTemplateDefaults::emailPurchaseOrderSubject(),
'body' => EmailTemplateDefaults::emailPurchaseOrderTemplate(),
],
];
Cache::forever($name, $data);

View File

@ -120,6 +120,9 @@ trait CompanySettingsSaver
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') {
$value = 'integer';
if($key == 'besr_id')
$value = 'string';
if (! property_exists($settings, $key)) {
continue;
} elseif (! $this->checkAttribute($value, $settings->{$key})) {
@ -187,6 +190,9 @@ trait CompanySettingsSaver
if($key == 'gmail_sending_user_id')
$value = 'string';
if($key == 'besr_id')
$value = 'string';
if (! property_exists($settings, $key)) {
continue;
} elseif ($this->checkAttribute($value, $settings->{$key})) {

View File

@ -200,56 +200,33 @@ trait MakesTemplateData
$data['$task.tax_name3'] = ['value' => 'CA Sales Tax', 'label' => ctrans('texts.tax')];
$data['$task.line_total'] = ['value' => '$100.00', 'label' => ctrans('texts.line_total')];
//$data['$paid_to_date'] = ;
// $data['$your_invoice'] = ;
// $data['$quote'] = ;
// $data['$your_quote'] = ;
// $data['$invoice_issued_to'] = ;
// $data['$quote_issued_to'] = ;
// $data['$rate'] = ;
// $data['$hours'] = ;
// $data['$from'] = ;
// $data['$to'] = ;
// $data['$invoice_to'] = ;
// $data['$quote_to'] = ;
// $data['$details'] = ;
// $data['custom_label1'] = ['value' => '', 'label' => ctrans('texts.')];
// $data['custom_label2'] = ['value' => '', 'label' => ctrans('texts.')];
// $data['custom_label3'] = ['value' => '', 'label' => ctrans('texts.')];
// $data['custom_label4'] = ['value' => '', 'label' => ctrans('texts.')];
//$data['$blank'] = ;
//$data['$surcharge'] = ;
/*
$data['$tax_invoice'] =
$data['$tax_quote'] =
$data['$statement'] = ;
$data['$statement_date'] = ;
$data['$your_statement'] = ;
$data['$statement_issued_to'] = ;
$data['$statement_to'] = ;
$data['$credit_note'] = ;
$data['$credit_date'] = ;
$data['$credit_issued_to'] = ;
$data['$credit_to'] = ;
$data['$your_credit'] = ;
$data['$phone'] = ;
$data['$vendor_name'] = ['value' => 'Joey Diaz Denkins', 'label' => ctrans('texts.vendor_name')];;
$data['$vendor.name'] = &$data['$vendor_name'];
$data['$vendor'] = &$data['$vendor_name'];
$data['$outstanding'] = ;
$data['$invoice_due_date'] = ;
$data['$quote_due_date'] = ;
$data['$service'] = ;
$data['$product_key'] = ;
$data['$unit_cost'] = ;
$data['$custom_value1'] = ;
$data['$custom_value2'] = ;
$data['$delivery_note'] = ;
$data['$date'] = ;
$data['$method'] = ;
$data['$payment_date'] = ;
$data['$reference'] = ;
$data['$amount'] = ;
$data['$amount_paid'] =;
*/
$data['$vendor.address1'] = &$data['$address1'];
$data['$vendor.address2'] = &$data['$address2'];
$data['$vendor_address'] = ['value' => '5 Kalamazoo Way\n Jimbuckeroo\n USA 90210', 'label' => ctrans('texts.address')];
$data['$vendor.address'] = &$data['$vendor_address'];
$data['$vendor.postal_code'] = ['value' => '90210', 'label' => ctrans('texts.postal_code')];
$data['$vendor.public_notes'] = $data['$invoice.public_notes'];
$data['$vendor.city'] = &$data['$company.city'];
$data['$vendor.state'] = &$data['$company.state'];
$data['$vendor.id_number'] = &$data['$id_number'];
$data['$vendor.vat_number'] = &$data['$vat_number'];
$data['$vendor.website'] = &$data['$website'];
$data['$vendor.phone'] = &$data['$phone'];
$data['$vendor.city_state_postal'] = &$data['$city_state_postal'];
$data['$vendor.postal_city_state'] = &$data['$postal_city_state'];
$data['$vendor.country'] = &$data['$country'];
$data['$vendor.email'] = &$data['$email'];
$data['$vendor.billing_address1'] = &$data['$vendor.address1'];
$data['$vendor.billing_address2'] = &$data['$vendor.address2'];
$data['$vendor.billing_city'] = &$data['$vendor.city'];
$data['$vendor.billing_state'] = &$data['$vendor.state'];
$data['$vendor.billing_postal_code'] = &$data['$vendor.postal_code'];
$data['$vendor.billing_country'] = &$data['$vendor.country'];
$arrKeysLength = array_map('strlen', array_keys($data));
array_multisort($arrKeysLength, SORT_DESC, $data);

View File

@ -55,7 +55,7 @@ trait SettingsSaver
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter' || ($key == 'payment_terms' && strlen($settings->{$key}) >= 1) || ($key == 'valid_until' && strlen($settings->{$key}) >= 1)) {
$value = 'integer';
if($key == 'gmail_sending_user_id')
if($key == 'gmail_sending_user_id' || $key == 'besr_id')
$value = 'string';
if (! property_exists($settings, $key)) {

View File

@ -63,6 +63,11 @@ class VendorHtmlEngine
$this->vendor = $this->contact->vendor->load('company','country');
if(!$this->vendor->currency_id){
$this->vendor->currency_id = $this->company->settings->currency_id;
$this->vendor->save();
}
$this->entity->load('vendor');
$this->settings = $this->company->settings;

View File

@ -85,6 +85,7 @@
"setasign/fpdi": "^2.3",
"socialiteproviders/apple": "^5.2",
"socialiteproviders/microsoft": "^4.1",
"sprain/swiss-qr-bill": "^3.2",
"square/square": "13.0.0.20210721",
"stripe/stripe-php": "^7.50",
"symfony/http-client": "^5.2",

1167
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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.4.4',
'app_tag' => '5.4.4',
'app_version' => '5.4.7',
'app_tag' => '5.4.7',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,51 @@
<?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 Database\Factories;
use App\Factory\InvoiceItemFactory;
use App\Models\Invoice;
use App\Models\PurchaseOrder;
use Illuminate\Database\Eloquent\Factories\Factory;
class PurchaseOrderFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = PurchaseOrder::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'status_id' => Invoice::STATUS_SENT,
'number' => $this->faker->ean13(),
'discount' => $this->faker->numberBetween(1, 10),
'is_amount_discount' => (bool) random_int(0, 1),
'tax_name1' => 'GST',
'tax_rate1' => 10,
'tax_name2' => 'VAT',
'tax_rate2' => 17.5,
'is_deleted' => false,
'po_number' => $this->faker->text(10),
'date' => $this->faker->date(),
'due_date' => $this->faker->date(),
'line_items' => InvoiceItemFactory::generate(5),
'terms' => $this->faker->text(500),
];
}
}

View File

@ -0,0 +1,50 @@
<?php
use App\Models\Gateway;
use App\Models\GatewayType;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class FortePaymentGateway extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$fields = new \stdClass;
$fields->testMode = false;
$fields->apiLoginId = "";
$fields->apiAccessId = "";
$fields->secureKey = "";
$fields->authOrganizationId = "";
$fields->organizationId = "";
$fields->locationId = "";
$forte = new Gateway;
$forte->id = 59;
$forte->name = 'Forte';
$forte->key = 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs';
$forte->provider = 'Forte';
$forte->is_offsite = true;
$forte->fields = \json_encode($fields);
$forte->visible = 1;
$forte->site_url = 'https://www.forte.net/';
$forte->default_gateway_type_id = GatewayType::CREDIT_CARD;
$forte->save();
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -24,7 +24,7 @@ class AddJobRelatedFieldsToSchedulersTable extends Migration
Schema::table('schedulers', function (Blueprint $table) {
$table->string('action_name')->index();
$table->string('action_class');
$table->json('parameters')->nullable();
$table->mediumText('parameters')->nullable();
});
}

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class FixesForDescriptionInPdfDesigns extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
\Illuminate\Support\Facades\Artisan::call('ninja:design-update');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SetOauthExpiryColumn extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->datetime('oauth_user_token_expiry')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFlagToAccountsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function (Blueprint $table) {
$table->boolean('is_flagged')->default(0);
$table->boolean('is_verified_account')->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

View File

@ -98,7 +98,7 @@ class PaymentLibrariesSeeder extends Seeder
Gateway::query()->update(['visible' => 0]);
Gateway::whereIn('id', [1,3,7,11,15,20,39,46,55,50,57,52,58])->update(['visible' => 1]);
Gateway::whereIn('id', [1,3,7,11,15,20,39,46,55,50,57,52,58,59])->update(['visible' => 1]);
if (Ninja::isHosted()) {
Gateway::whereIn('id', [20])->update(['visible' => 0]);

View File

@ -2157,6 +2157,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
attributed_text
super_editor
super_text_layout
Copyright (c) 2021 Superlist, SuperDeclarative! and the contributors
@ -5969,7 +5970,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
cross_file
flutter_lints
flutter_plugin_android_lifecycle
google_sign_in
google_sign_in_android
@ -15096,37 +15096,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------------------
lints
Copyright 2021, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
markdown
@ -15239,6 +15208,31 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
msal_js
MIT License
Copyright (c) 2021 Ethan Lafrenais
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------------------
msix

View File

@ -3,43 +3,43 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"/": "2e739a78eec983322924f724ebfa09ba",
"main.dart.js": "fa4a0263712be1ce1df7d59ca0ede10e",
"version.json": "d72bd323e3b8e22ce5acdc247f4e6f62",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"canvaskit/canvaskit.wasm": "4b83d89d9fecbea8ca46f2f760c5a9ba",
"canvaskit/profiling/canvaskit.wasm": "95e736ab31147d1b2c7b25f11d4c32cd",
"canvaskit/profiling/canvaskit.js": "ae2949af4efc61d28a4a80fffa1db900",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"canvaskit/canvaskit.js": "c2b4e5f3d7a3d82aed024e7249a78487",
"canvaskit/profiling/canvaskit.js": "ae2949af4efc61d28a4a80fffa1db900",
"canvaskit/profiling/canvaskit.wasm": "95e736ab31147d1b2c7b25f11d4c32cd",
"canvaskit/canvaskit.wasm": "4b83d89d9fecbea8ca46f2f760c5a9ba",
"/": "0ba127947d7857ad97e39c264e308461",
"flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67",
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"version.json": "d72bd323e3b8e22ce5acdc247f4e6f62",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "b62641afc9ab487008e996a5c5865e56",
"assets/fonts/MaterialIcons-Regular.otf": "95db9098c58fd6db106f1116bae85a0b",
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3",
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
"assets/assets/images/logo_dark.png": "a233ed1d4d0f7414bf97a9a10f11fb0a",
"assets/NOTICES": "52d7174bb068ef86545951d5bc8c5744",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40"
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "b62641afc9ab487008e996a5c5865e56",
"assets/NOTICES": "9b6b63256d3a6491659b71127ee9f3b6",
"main.dart.js": "739fb98bd5601a93da8be6b4358508d2"
};
// The application shell files that are downloaded before a service worker can

View File

@ -0,0 +1,81 @@
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/*!************************************************************!*\
!*** ./resources/js/clients/payments/forte-ach-payment.js ***!
\************************************************************/
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
* 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://opensource.org/licenses/AAL
*/
var ForteAuthorizeACH = function ForteAuthorizeACH(apiLoginId) {
var _this = this;
_classCallCheck(this, ForteAuthorizeACH);
_defineProperty(this, "handleAuthorization", function () {
var account_number = document.getElementById('account-number').value;
var routing_number = document.getElementById('routing-number').value;
var data = {
api_login_id: _this.apiLoginId,
account_number: account_number,
routing_number: routing_number,
account_type: 'checking'
};
var payNowButton = document.getElementById('pay-now');
if (payNowButton) {
document.getElementById('pay-now').disabled = true;
document.querySelector('#pay-now > svg').classList.remove('hidden');
document.querySelector('#pay-now > span').classList.add('hidden');
} // console.log(data);
forte.createToken(data).success(_this.successResponseHandler).error(_this.failedResponseHandler);
return false;
});
_defineProperty(this, "successResponseHandler", function (response) {
document.getElementById('payment_token').value = response.onetime_token;
document.getElementById('server_response').submit();
return false;
});
_defineProperty(this, "failedResponseHandler", function (response) {
var errors = '<div class="alert alert-failure mb-4"><ul><li>' + response.response_description + '</li></ul></div>';
document.getElementById('forte_errors').innerHTML = errors;
document.getElementById('pay-now').disabled = false;
document.querySelector('#pay-now > svg').classList.add('hidden');
document.querySelector('#pay-now > span').classList.remove('hidden');
return false;
});
_defineProperty(this, "handle", function () {
var payNowButton = document.getElementById('pay-now');
if (payNowButton) {
payNowButton.addEventListener('click', function (e) {
_this.handleAuthorization();
});
}
return _this;
});
this.apiLoginId = apiLoginId;
};
var apiLoginId = document.querySelector('meta[name="forte-api-login-id"]').content;
/** @handle */
new ForteAuthorizeACH(apiLoginId).handle();
/******/ })()
;

View File

@ -0,0 +1,669 @@
(() => {
function t(t) {
(this.elem = jQuery(t)),
(this.captureName =
!!this.elem.data('capture-name') &&
this.elem.data('capture-name')),
(this.iconColour =
!!this.elem.data('icon-colour') &&
this.elem.data('icon-colour')),
(this.stripe =
!!this.elem.data('stripe') && this.elem.data('stripe')),
this.stripe && (this.captureName = !1),
this.initCardNumberInput(),
this.initNameInput(),
this.initExpiryMonthInput(),
this.initExpiryYearInput(),
this.initCvcInput(),
this.elem.empty(),
this.setupCardNumberInput(),
this.setupNameInput(),
this.setupExpiryInput(),
this.setupCvcInput(),
this.iconColour && this.setIconColour(this.iconColour),
this.refreshCreditCardTypeIcon();
}
!(function (e) {
var r = {
init: function () {
return this.data('cardjs', new t(this)), this;
},
cardNumber: function () {
return this.data('cardjs').getCardNumber();
},
cardType: function () {
return this.data('cardjs').getCardType();
},
name: function () {
return this.data('cardjs').getName();
},
expiryMonth: function () {
return this.data('cardjs').getExpiryMonth();
},
expiryYear: function () {
return this.data('cardjs').getExpiryYear();
},
cvc: function () {
return this.data('cardjs').getCvc();
},
};
e.fn.CardJs = function (t) {
return r[t]
? r[t].apply(this, Array.prototype.slice.call(arguments, 1))
: 'object' != typeof t && t
? void e.error(
'Method ' + t + ' does not exist on jQuery.CardJs'
)
: r.init.apply(this, arguments);
};
})(jQuery),
$(function () {
$('.card-js').each(function (t, e) {
$(e).CardJs();
});
}),
(t.prototype.constructor = t),
(t.KEYS = {
0: 48,
9: 57,
NUMPAD_0: 96,
NUMPAD_9: 105,
DELETE: 46,
BACKSPACE: 8,
ARROW_LEFT: 37,
ARROW_RIGHT: 39,
ARROW_UP: 38,
ARROW_DOWN: 40,
HOME: 36,
END: 35,
TAB: 9,
A: 65,
X: 88,
C: 67,
V: 86,
}),
(t.CREDIT_CARD_NUMBER_DEFAULT_MASK = 'XXXX XXXX XXXX XXXX'),
(t.CREDIT_CARD_NUMBER_VISA_MASK = 'XXXX XXXX XXXX XXXX'),
(t.CREDIT_CARD_NUMBER_MASTERCARD_MASK = 'XXXX XXXX XXXX XXXX'),
(t.CREDIT_CARD_NUMBER_DISCOVER_MASK = 'XXXX XXXX XXXX XXXX'),
(t.CREDIT_CARD_NUMBER_JCB_MASK = 'XXXX XXXX XXXX XXXX'),
(t.CREDIT_CARD_NUMBER_AMEX_MASK = 'XXXX XXXXXX XXXXX'),
(t.CREDIT_CARD_NUMBER_DINERS_MASK = 'XXXX XXXX XXXX XX'),
(t.prototype.creditCardNumberMask = t.CREDIT_CARD_NUMBER_DEFAULT_MASK),
(t.CREDIT_CARD_NUMBER_PLACEHOLDER = 'Card number'),
(t.NAME_PLACEHOLDER = 'Name on card'),
(t.EXPIRY_MASK = 'XX / XXXX'),
(t.EXPIRY_PLACEHOLDER = 'MM / YYYY'),
(t.EXPIRY_USE_DROPDOWNS = !1),
(t.EXPIRY_NUMBER_OF_YEARS = 10),
(t.CVC_MASK_3 = 'XXX'),
(t.CVC_MASK_4 = 'XXXX'),
(t.CVC_PLACEHOLDER = 'CVC'),
(t.CREDIT_CARD_SVG =
'<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="3px" width="24px" height="17px" viewBox="0 0 216 146" enable-background="new 0 0 216 146" xml:space="preserve"><g><path class="svg" d="M182.385,14.258c-2.553-2.553-5.621-3.829-9.205-3.829H42.821c-3.585,0-6.653,1.276-9.207,3.829c-2.553,2.553-3.829,5.621-3.829,9.206v99.071c0,3.585,1.276,6.654,3.829,9.207c2.554,2.553,5.622,3.829,9.207,3.829H173.18c3.584,0,6.652-1.276,9.205-3.829s3.83-5.622,3.83-9.207V23.464C186.215,19.879,184.938,16.811,182.385,14.258z M175.785,122.536c0,0.707-0.258,1.317-0.773,1.834c-0.516,0.515-1.127,0.772-1.832,0.772H42.821c-0.706,0-1.317-0.258-1.833-0.773c-0.516-0.518-0.774-1.127-0.774-1.834V73h135.571V122.536z M175.785,41.713H40.214v-18.25c0-0.706,0.257-1.316,0.774-1.833c0.516-0.515,1.127-0.773,1.833-0.773H173.18c0.705,0,1.316,0.257,1.832,0.773c0.516,0.517,0.773,1.127,0.773,1.833V41.713z"/><rect class="svg" x="50.643" y="104.285" width="20.857" height="10.429"/><rect class="svg" x="81.929" y="104.285" width="31.286" height="10.429"/></g></svg>'),
(t.LOCK_SVG =
'<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="3px" width="24px" height="17px" viewBox="0 0 216 146" enable-background="new 0 0 216 146" xml:space="preserve"><path class="svg" d="M152.646,70.067c-1.521-1.521-3.367-2.281-5.541-2.281H144.5V52.142c0-9.994-3.585-18.575-10.754-25.745c-7.17-7.17-15.751-10.755-25.746-10.755s-18.577,3.585-25.746,10.755C75.084,33.567,71.5,42.148,71.5,52.142v15.644h-2.607c-2.172,0-4.019,0.76-5.54,2.281c-1.521,1.52-2.281,3.367-2.281,5.541v46.929c0,2.172,0.76,4.019,2.281,5.54c1.521,1.52,3.368,2.281,5.54,2.281h78.214c2.174,0,4.02-0.76,5.541-2.281c1.52-1.521,2.281-3.368,2.281-5.54V75.607C154.93,73.435,154.168,71.588,152.646,70.067z M128.857,67.786H87.143V52.142c0-5.757,2.037-10.673,6.111-14.746c4.074-4.074,8.989-6.11,14.747-6.11s10.673,2.036,14.746,6.11c4.073,4.073,6.11,8.989,6.11,14.746V67.786z"/></svg>'),
(t.CALENDAR_SVG =
'<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="4px" width="24px" height="16px" viewBox="0 0 216 146" enable-background="new 0 0 216 146" xml:space="preserve"><path class="svg" d="M172.691,23.953c-2.062-2.064-4.508-3.096-7.332-3.096h-10.428v-7.822c0-3.584-1.277-6.653-3.83-9.206c-2.554-2.553-5.621-3.83-9.207-3.83h-5.213c-3.586,0-6.654,1.277-9.207,3.83c-2.554,2.553-3.83,5.622-3.83,9.206v7.822H92.359v-7.822c0-3.584-1.277-6.653-3.83-9.206c-2.553-2.553-5.622-3.83-9.207-3.83h-5.214c-3.585,0-6.654,1.277-9.207,3.83c-2.553,2.553-3.83,5.622-3.83,9.206v7.822H50.643c-2.825,0-5.269,1.032-7.333,3.096s-3.096,4.509-3.096,7.333v104.287c0,2.823,1.032,5.267,3.096,7.332c2.064,2.064,4.508,3.096,7.333,3.096h114.714c2.824,0,5.27-1.032,7.332-3.096c2.064-2.064,3.096-4.509,3.096-7.332V31.286C175.785,28.461,174.754,26.017,172.691,23.953z M134.073,13.036c0-0.761,0.243-1.386,0.731-1.874c0.488-0.488,1.113-0.733,1.875-0.733h5.213c0.762,0,1.385,0.244,1.875,0.733c0.488,0.489,0.732,1.114,0.732,1.874V36.5c0,0.761-0.244,1.385-0.732,1.874c-0.49,0.488-1.113,0.733-1.875,0.733h-5.213c-0.762,0-1.387-0.244-1.875-0.733s-0.731-1.113-0.731-1.874V13.036z M71.501,13.036c0-0.761,0.244-1.386,0.733-1.874c0.489-0.488,1.113-0.733,1.874-0.733h5.214c0.761,0,1.386,0.244,1.874,0.733c0.488,0.489,0.733,1.114,0.733,1.874V36.5c0,0.761-0.244,1.386-0.733,1.874c-0.489,0.488-1.113,0.733-1.874,0.733h-5.214c-0.761,0-1.386-0.244-1.874-0.733c-0.488-0.489-0.733-1.113-0.733-1.874V13.036z M165.357,135.572H50.643V52.143h114.714V135.572z"/></svg>'),
(t.USER_SVG =
'<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="4px" width="24px" height="16px" viewBox="0 0 216 146" enable-background="new 0 0 216 146" xml:space="preserve"><g><path class="svg" d="M107.999,73c8.638,0,16.011-3.056,22.12-9.166c6.111-6.11,9.166-13.483,9.166-22.12c0-8.636-3.055-16.009-9.166-22.12c-6.11-6.11-13.484-9.165-22.12-9.165c-8.636,0-16.01,3.055-22.12,9.165c-6.111,6.111-9.166,13.484-9.166,22.12c0,8.637,3.055,16.01,9.166,22.12C91.99,69.944,99.363,73,107.999,73z"/><path class="svg" d="M165.07,106.037c-0.191-2.743-0.571-5.703-1.141-8.881c-0.57-3.178-1.291-6.124-2.16-8.84c-0.869-2.715-2.037-5.363-3.504-7.943c-1.466-2.58-3.15-4.78-5.052-6.6s-4.223-3.272-6.965-4.358c-2.744-1.086-5.772-1.63-9.085-1.63c-0.489,0-1.63,0.584-3.422,1.752s-3.815,2.472-6.069,3.911c-2.254,1.438-5.188,2.743-8.799,3.909c-3.612,1.168-7.237,1.752-10.877,1.752c-3.639,0-7.264-0.584-10.876-1.752c-3.611-1.166-6.545-2.471-8.799-3.909c-2.254-1.439-4.277-2.743-6.069-3.911c-1.793-1.168-2.933-1.752-3.422-1.752c-3.313,0-6.341,0.544-9.084,1.63s-5.065,2.539-6.966,4.358c-1.901,1.82-3.585,4.02-5.051,6.6s-2.634,5.229-3.503,7.943c-0.869,2.716-1.589,5.662-2.159,8.84c-0.571,3.178-0.951,6.137-1.141,8.881c-0.19,2.744-0.285,5.554-0.285,8.433c0,6.517,1.983,11.664,5.948,15.439c3.965,3.774,9.234,5.661,15.806,5.661h71.208c6.572,0,11.84-1.887,15.806-5.661c3.966-3.775,5.948-8.921,5.948-15.439C165.357,111.591,165.262,108.78,165.07,106.037z"/></g></svg>'),
(t.MAIL_SVG =
'<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"x="0px" y="4px" width="24px" height="16px" viewBox="0 0 216 146" enable-background="new 0 0 216 146" xml:space="preserve"><path class="svg" d="M177.171,19.472c-2.553-2.553-5.622-3.829-9.206-3.829H48.036c-3.585,0-6.654,1.276-9.207,3.829C36.276,22.025,35,25.094,35,28.679v88.644c0,3.585,1.276,6.652,3.829,9.205c2.553,2.555,5.622,3.83,9.207,3.83h119.929c3.584,0,6.653-1.275,9.206-3.83c2.554-2.553,3.829-5.621,3.829-9.205V28.679C181,25.094,179.725,22.025,177.171,19.472zM170.57,117.321c0,0.706-0.258,1.317-0.774,1.833s-1.127,0.773-1.832,0.773H48.035c-0.706,0-1.317-0.257-1.833-0.773c-0.516-0.516-0.774-1.127-0.774-1.833V54.75c1.738,1.955,3.612,3.748,5.622,5.377c14.557,11.189,26.126,20.368,34.708,27.538c2.77,2.336,5.024,4.155,6.762,5.459s4.087,2.62,7.047,3.951s5.744,1.995,8.351,1.995H108h0.081c2.606,0,5.392-0.664,8.351-1.995c2.961-1.331,5.311-2.647,7.049-3.951c1.737-1.304,3.992-3.123,6.762-5.459c8.582-7.17,20.15-16.349,34.707-27.538c2.01-1.629,3.885-3.422,5.621-5.377V117.321z M170.57,30.797v0.896c0,3.204-1.262,6.776-3.787,10.713c-2.525,3.938-5.256,7.075-8.188,9.41c-10.484,8.257-21.373,16.865-32.672,25.827c-0.326,0.271-1.277,1.073-2.852,2.403c-1.574,1.331-2.824,2.351-3.748,3.056c-0.924,0.707-2.131,1.562-3.625,2.566s-2.865,1.752-4.114,2.24s-2.417,0.732-3.503,0.732H108h-0.082c-1.086,0-2.253-0.244-3.503-0.732c-1.249-0.488-2.621-1.236-4.114-2.24c-1.493-1.004-2.702-1.859-3.625-2.566c-0.923-0.705-2.173-1.725-3.748-3.056c-1.575-1.33-2.526-2.132-2.852-2.403c-11.297-8.962-22.187-17.57-32.67-25.827c-7.985-6.3-11.977-14.013-11.977-23.138c0-0.706,0.258-1.317,0.774-1.833c0.516-0.516,1.127-0.774,1.833-0.774h119.929c0.434,0.244,0.814,0.312,1.141,0.204c0.326-0.11,0.57,0.094,0.732,0.61c0.163,0.516,0.312,0.76,0.448,0.733c0.136-0.027,0.218,0.312,0.245,1.019c0.025,0.706,0.039,1.061,0.039,1.061V30.797z"/></svg>'),
(t.INFORMATION_SVG =
'<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="4px" width="24px" height="16px" viewBox="0 0 216 146" enable-background="new 0 0 216 146" xml:space="preserve"><g><path class="svg" d="M97.571,41.714h20.859c1.411,0,2.633-0.516,3.666-1.548c1.031-1.031,1.547-2.254,1.547-3.666V20.857c0-1.412-0.516-2.634-1.549-3.667c-1.031-1.031-2.254-1.548-3.666-1.548H97.571c-1.412,0-2.634,0.517-3.666,1.548c-1.032,1.032-1.548,2.255-1.548,3.667V36.5c0,1.412,0.516,2.635,1.548,3.666C94.937,41.198,96.159,41.714,97.571,41.714z"/><path class="svg" d="M132.523,111.048c-1.031-1.032-2.254-1.548-3.666-1.548h-5.215V62.571c0-1.412-0.516-2.634-1.547-3.666c-1.033-1.032-2.255-1.548-3.666-1.548H87.143c-1.412,0-2.634,0.516-3.666,1.548c-1.032,1.032-1.548,2.254-1.548,3.666V73c0,1.412,0.516,2.635,1.548,3.666c1.032,1.032,2.254,1.548,3.666,1.548h5.215V109.5h-5.215c-1.412,0-2.634,0.516-3.666,1.548c-1.032,1.032-1.548,2.254-1.548,3.666v10.429c0,1.412,0.516,2.635,1.548,3.668c1.032,1.03,2.254,1.547,3.666,1.547h41.714c1.412,0,2.634-0.517,3.666-1.547c1.031-1.033,1.547-2.256,1.547-3.668v-10.429C134.07,113.302,133.557,112.08,132.523,111.048z"/></g></svg>'),
(t.keyCodeFromEvent = function (t) {
return t.which || t.keyCode;
}),
(t.keyIsCommandFromEvent = function (t) {
return t.ctrlKey || t.metaKey;
}),
(t.keyIsNumber = function (e) {
return t.keyIsTopNumber(e) || t.keyIsKeypadNumber(e);
}),
(t.keyIsTopNumber = function (e) {
var r = t.keyCodeFromEvent(e);
return r >= t.KEYS[0] && r <= t.KEYS[9];
}),
(t.keyIsKeypadNumber = function (e) {
var r = t.keyCodeFromEvent(e);
return r >= t.KEYS.NUMPAD_0 && r <= t.KEYS.NUMPAD_9;
}),
(t.keyIsDelete = function (e) {
return t.keyCodeFromEvent(e) == t.KEYS.DELETE;
}),
(t.keyIsBackspace = function (e) {
return t.keyCodeFromEvent(e) == t.KEYS.BACKSPACE;
}),
(t.keyIsDeletion = function (e) {
return t.keyIsDelete(e) || t.keyIsBackspace(e);
}),
(t.keyIsArrow = function (e) {
var r = t.keyCodeFromEvent(e);
return r >= t.KEYS.ARROW_LEFT && r <= t.KEYS.ARROW_DOWN;
}),
(t.keyIsNavigation = function (e) {
var r = t.keyCodeFromEvent(e);
return r == t.KEYS.HOME || r == t.KEYS.END;
}),
(t.keyIsKeyboardCommand = function (e) {
var r = t.keyCodeFromEvent(e);
return (
t.keyIsCommandFromEvent(e) &&
(r == t.KEYS.A ||
r == t.KEYS.X ||
r == t.KEYS.C ||
r == t.KEYS.V)
);
}),
(t.keyIsTab = function (e) {
return t.keyCodeFromEvent(e) == t.KEYS.TAB;
}),
(t.copyAllElementAttributes = function (t, e) {
$.each(t[0].attributes, function (t, r) {
e.attr(r.nodeName, r.nodeValue);
});
}),
(t.numbersOnlyString = function (t) {
for (var e = '', r = 0; r < t.length; r++) {
var n = t.charAt(r);
!isNaN(parseInt(n)) && (e += n);
}
return e;
}),
(t.applyFormatMask = function (t, e) {
for (var r = '', n = 0, i = 0; i < e.length; i++) {
var a = e[i];
if ('X' == a) {
if (!t.charAt(n)) break;
(r += t.charAt(n)), n++;
} else r += a;
}
return r;
}),
(t.cardTypeFromNumber = function (t) {
if (((e = new RegExp('^30[0-5]')), null != t.match(e)))
return 'Diners - Carte Blanche';
if (((e = new RegExp('^(30[6-9]|36|38)')), null != t.match(e)))
return 'Diners';
if (((e = new RegExp('^35(2[89]|[3-8][0-9])')), null != t.match(e)))
return 'JCB';
if (((e = new RegExp('^3[47]')), null != t.match(e))) return 'AMEX';
if (
((e = new RegExp('^(4026|417500|4508|4844|491(3|7))')),
null != t.match(e))
)
return 'Visa Electron';
var e = new RegExp('^4');
return null != t.match(e)
? 'Visa'
: ((e = new RegExp('^5[1-5]')),
null != t.match(e)
? 'Mastercard'
: ((e = new RegExp(
'^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)'
)),
null != t.match(e) ? 'Discover' : ''));
}),
(t.caretStartPosition = function (t) {
return 'number' == typeof t.selectionStart && t.selectionStart;
}),
(t.caretEndPosition = function (t) {
return 'number' == typeof t.selectionEnd && t.selectionEnd;
}),
(t.setCaretPosition = function (t, e) {
if (null != t)
if (t.createTextRange) {
var r = t.createTextRange();
r.move('character', e), r.select();
} else
t.selectionStart
? (t.focus(), t.setSelectionRange(e, e))
: t.focus();
}),
(t.normaliseCaretPosition = function (t, e) {
var r = 0;
if (0 > e || e > t.length) return 0;
for (var n = 0; n < t.length; n++) {
if (n == e) return r;
'X' == t[n] && r++;
}
return r;
}),
(t.denormaliseCaretPosition = function (t, e) {
var r = 0;
if (0 > e || e > t.length) return 0;
for (var n = 0; n < t.length; n++) {
if (r == e) return n;
'X' == t[n] && r++;
}
return t.length;
}),
(t.filterNumberOnlyKey = function (e) {
var r = t.keyIsNumber(e),
n = t.keyIsDeletion(e),
i = t.keyIsArrow(e),
a = t.keyIsNavigation(e),
s = t.keyIsKeyboardCommand(e),
p = t.keyIsTab(e);
r || n || i || a || s || p || e.preventDefault();
}),
(t.digitFromKeyCode = function (e) {
return e >= t.KEYS[0] && e <= t.KEYS[9]
? e - t.KEYS[0]
: e >= t.KEYS.NUMPAD_0 && e <= t.KEYS.NUMPAD_9
? e - t.KEYS.NUMPAD_0
: null;
}),
(t.handleMaskedNumberInputKey = function (e, r) {
t.filterNumberOnlyKey(e);
var n = e.which || e.keyCode,
i = e.target,
a = t.caretStartPosition(i),
s = t.caretEndPosition(i),
p = t.normaliseCaretPosition(r, a),
c = t.normaliseCaretPosition(r, s),
o = a,
u = t.keyIsNumber(e),
h = t.keyIsDelete(e),
d = t.keyIsBackspace(e);
if (u || h || d) {
e.preventDefault();
var l = $(i).val(),
y = t.numbersOnlyString(l),
m = t.digitFromKeyCode(n),
C = c > p;
C && (y = y.slice(0, p) + y.slice(c)),
a != r.length &&
(u &&
l.length <= r.length &&
((y = y.slice(0, p) + m + y.slice(p)),
(o = Math.max(
t.denormaliseCaretPosition(r, p + 1),
t.denormaliseCaretPosition(r, p + 2) - 1
))),
h && (y = y.slice(0, p) + y.slice(p + 1))),
0 != a &&
d &&
!C &&
((y = y.slice(0, p - 1) + y.slice(p)),
(o = t.denormaliseCaretPosition(r, p - 1))),
$(i).val(t.applyFormatMask(y, r)),
t.setCaretPosition(i, o);
}
}),
(t.handleCreditCardNumberKey = function (e, r) {
t.handleMaskedNumberInputKey(e, r);
}),
(t.handleCreditCardNumberChange = function (t) {}),
(t.handleExpiryKey = function (e) {
t.handleMaskedNumberInputKey(e, t.EXPIRY_MASK);
}),
(t.prototype.getCardNumber = function () {
return this.cardNumberInput.val();
}),
(t.prototype.getCardType = function () {
return t.cardTypeFromNumber(this.getCardNumber());
}),
(t.prototype.getName = function () {
return this.nameInput.val();
}),
(t.prototype.getExpiryMonth = function () {
return this.expiryMonthInput.val();
}),
(t.prototype.getExpiryYear = function () {
return this.expiryYearInput.val();
}),
(t.prototype.getCvc = function () {
return this.cvcInput.val();
}),
(t.prototype.setIconColour = function (t) {
this.elem.find('.icon .svg').css({ fill: t });
}),
(t.prototype.setIconColour = function (t) {
this.elem.find('.icon .svg').css({ fill: t });
}),
(t.prototype.refreshCreditCardTypeIcon = function () {
this.setCardTypeIconFromNumber(
t.numbersOnlyString(this.cardNumberInput.val())
);
}),
(t.prototype.clearCardTypeIcon = function () {
this.elem
.find('.card-number-wrapper .card-type-icon')
.removeClass('show');
}),
(t.prototype.setCardTypeIconAsVisa = function () {
this.elem
.find('.card-number-wrapper .card-type-icon')
.attr('class', 'card-type-icon show visa');
}),
(t.prototype.setCardTypeIconAsMasterCard = function () {
this.elem
.find('.card-number-wrapper .card-type-icon')
.attr('class', 'card-type-icon show master-card');
}),
(t.prototype.setCardTypeIconAsAmericanExpress = function () {
this.elem
.find('.card-number-wrapper .card-type-icon')
.attr('class', 'card-type-icon show american-express');
}),
(t.prototype.setCardTypeIconAsDiscover = function () {
this.elem
.find('.card-number-wrapper .card-type-icon')
.attr('class', 'card-type-icon show discover');
}),
(t.prototype.setCardTypeIconAsDiners = function () {
this.elem
.find('.card-number-wrapper .card-type-icon')
.attr('class', 'card-type-icon show diners');
}),
(t.prototype.setCardTypeIconAsJcb = function () {
this.elem
.find('.card-number-wrapper .card-type-icon')
.attr('class', 'card-type-icon show jcb');
}),
(t.prototype.setCardTypeIconFromNumber = function (e) {
switch (t.cardTypeFromNumber(e)) {
case 'Visa Electron':
case 'Visa':
this.setCardTypeAsVisa();
break;
case 'Mastercard':
this.setCardTypeAsMasterCard();
break;
case 'AMEX':
this.setCardTypeAsAmericanExpress();
break;
case 'Discover':
this.setCardTypeAsDiscover();
break;
case 'Diners - Carte Blanche':
case 'Diners':
this.setCardTypeAsDiners();
break;
case 'JCB':
this.setCardTypeAsJcb();
break;
default:
this.clearCardType();
}
}),
(t.prototype.setCardMask = function (t) {
(this.creditCardNumberMask = t),
this.cardNumberInput.attr('maxlength', t.length);
}),
(t.prototype.setCvc3 = function () {
this.cvcInput.attr('maxlength', t.CVC_MASK_3.length);
}),
(t.prototype.setCvc4 = function () {
this.cvcInput.attr('maxlength', t.CVC_MASK_4.length);
}),
(t.prototype.clearCardType = function () {
this.clearCardTypeIcon(),
this.setCardMask(t.CREDIT_CARD_NUMBER_DEFAULT_MASK),
this.setCvc3();
}),
(t.prototype.setCardTypeAsVisa = function () {
this.setCardTypeIconAsVisa(),
this.setCardMask(t.CREDIT_CARD_NUMBER_VISA_MASK),
this.setCvc3();
}),
(t.prototype.setCardTypeAsMasterCard = function () {
this.setCardTypeIconAsMasterCard(),
this.setCardMask(t.CREDIT_CARD_NUMBER_MASTERCARD_MASK),
this.setCvc3();
}),
(t.prototype.setCardTypeAsAmericanExpress = function () {
this.setCardTypeIconAsAmericanExpress(),
this.setCardMask(t.CREDIT_CARD_NUMBER_AMEX_MASK),
this.setCvc4();
}),
(t.prototype.setCardTypeAsDiscover = function () {
this.setCardTypeIconAsDiscover(),
this.setCardMask(t.CREDIT_CARD_NUMBER_DISCOVER_MASK),
this.setCvc3();
}),
(t.prototype.setCardTypeAsDiners = function () {
this.setCardTypeIconAsDiners(),
this.setCardMask(t.CREDIT_CARD_NUMBER_DINERS_MASK),
this.setCvc3();
}),
(t.prototype.setCardTypeAsJcb = function () {
this.setCardTypeIconAsJcb(),
this.setCardMask(t.CREDIT_CARD_NUMBER_JCB_MASK),
this.setCvc3();
}),
(t.prototype.initCardNumberInput = function () {
var e = this;
(this.cardNumberInput = this.elem.find('.card-number')),
this.cardNumberInput[0]
? this.cardNumberInput.detach()
: (this.cardNumberInput = $(
"<input class='card-number' />"
)),
this.cardNumberInput.attr('type', 'tel'),
this.cardNumberInput.attr('placeholder') ||
this.cardNumberInput.attr(
'placeholder',
t.CREDIT_CARD_NUMBER_PLACEHOLDER
),
this.cardNumberInput.attr(
'maxlength',
this.creditCardNumberMask.length
),
this.cardNumberInput.attr('x-autocompletetype', 'cc-number'),
this.cardNumberInput.attr('autocompletetype', 'cc-number'),
this.cardNumberInput.attr('autocorrect', 'off'),
this.cardNumberInput.attr('spellcheck', 'off'),
this.cardNumberInput.attr('autocapitalize', 'off'),
this.cardNumberInput.keydown(function (r) {
t.handleCreditCardNumberKey(r, e.creditCardNumberMask);
}),
this.cardNumberInput.keyup(function (t) {
e.refreshCreditCardTypeIcon();
}),
this.cardNumberInput.change(t.handleCreditCardNumberChange);
}),
(t.prototype.initNameInput = function () {
(this.nameInput = this.elem.find('.name')),
this.nameInput[0]
? ((this.captureName = !0), this.nameInput.detach())
: (this.nameInput = $("<input class='name' />")),
this.nameInput.attr('placeholder') ||
this.nameInput.attr('placeholder', t.NAME_PLACEHOLDER);
}),
(t.prototype.initExpiryMonthInput = function () {
(this.expiryMonthInput = this.elem.find('.expiry-month')),
this.expiryMonthInput[0]
? this.expiryMonthInput.detach()
: (this.expiryMonthInput = $(
"<input class='expiry-month' />"
));
}),
(t.prototype.initExpiryYearInput = function () {
(this.expiryYearInput = this.elem.find('.expiry-year')),
this.expiryYearInput[0]
? this.expiryYearInput.detach()
: (this.expiryYearInput = $(
"<input class='expiry-year' name='expiry-year' />"
));
}),
(t.prototype.initCvcInput = function () {
(this.cvcInput = this.elem.find('.cvc')),
this.cvcInput[0]
? this.cvcInput.detach()
: (this.cvcInput = $("<input class='cvc' />")),
this.cvcInput.attr('type', 'tel'),
this.cvcInput.attr('placeholder') ||
this.cvcInput.attr('placeholder', t.CVC_PLACEHOLDER),
this.cvcInput.attr('maxlength', t.CVC_MASK_3.length),
this.cvcInput.attr('x-autocompletetype', 'cc-csc'),
this.cvcInput.attr('autocompletetype', 'cc-csc'),
this.cvcInput.attr('autocorrect', 'off'),
this.cvcInput.attr('spellcheck', 'off'),
this.cvcInput.attr('autocapitalize', 'off'),
this.cvcInput.keydown(t.filterNumberOnlyKey);
}),
(t.prototype.setupCardNumberInput = function () {
this.stripe && this.cardNumberInput.attr('data-stripe', 'number'),
this.elem.append("<div class='card-number-wrapper'></div>");
var e = this.elem.find('.card-number-wrapper');
e.append(this.cardNumberInput),
e.append("<div class='card-type-icon'></div>"),
e.append("<div class='icon'></div>"),
e.find('.icon').append(t.CREDIT_CARD_SVG);
}),
(t.prototype.setupNameInput = function () {
if (this.captureName) {
this.elem.append("<div class='name-wrapper'></div>");
var e = this.elem.find('.name-wrapper');
e.append(this.nameInput),
e.append("<div class='icon'></div>"),
e.find('.icon').append(t.USER_SVG);
}
}),
(t.prototype.setupExpiryInput = function () {
this.elem.append(
"<div class='expiry-container'><div class='expiry-wrapper'></div></div>"
);
var e,
r = this.elem.find('.expiry-wrapper');
if (this.EXPIRY_USE_DROPDOWNS) {
e = $('<div></div>');
var n = $(
"<select><option value='any' selected='' hidden=''>MM</option><option value='1'>01</option><option value='2'>02</option><option value='3'>03</option><option value='4'>04</option><option value='5'>05</option><option value='6'>06</option><option value='7'>07</option><option value='8'>08</option><option value='9'>09</option><option value='10'>10</option><option value='11'>11</option><option value='12'>12</option></select>"
),
i = this.expiryMonthInput;
t.copyAllElementAttributes(i, n),
this.expiryMonthInput.remove(),
(this.expiryMonthInput = n);
for (
var a = $(
"<select><option value='any' selected='' hidden=''>YY</option></select>"
),
s = parseInt(
new Date().getFullYear().toString().substring(2, 4)
),
p = 0;
p < t.EXPIRY_NUMBER_OF_YEARS;
p++
)
a.append("<option value='" + s + "'>" + s + '</option>'),
(s = (s + 1) % 100);
var c = this.expiryYearInput;
t.copyAllElementAttributes(c, a),
this.expiryYearInput.remove(),
(this.expiryYearInput = a),
e.append(this.expiryMonthInput),
e.append(this.expiryYearInput);
} else {
(e = $('<div></div>')),
(this.expiryMonthInput = $(
"<input type='hidden' name='expiry-month' />"
)),
(this.expiryYearInput = $(
"<input type='hidden' name='expiry-year' />"
)),
this.stripe &&
(this.expiryMonthInput.attr('data-stripe', 'exp-month'),
this.expiryYearInput.attr('data-stripe', 'exp-year')),
(this.expiryMonthYearInput = $("<input class='expiry' />")),
this.expiryMonthYearInput.attr('type', 'tel'),
this.expiryMonthYearInput.attr('placeholder') ||
this.expiryMonthYearInput.attr(
'placeholder',
t.EXPIRY_PLACEHOLDER
),
this.expiryMonthYearInput.attr(
'maxlength',
t.EXPIRY_MASK.length
),
this.expiryMonthYearInput.attr(
'x-autocompletetype',
'cc-exp'
),
this.expiryMonthYearInput.attr(
'autocompletetype',
'cc-exp'
),
this.expiryMonthYearInput.attr('autocorrect', 'off'),
this.expiryMonthYearInput.attr('spellcheck', 'off'),
this.expiryMonthYearInput.attr('autocapitalize', 'off');
var o = this;
this.expiryMonthYearInput.keydown(function (e) {
t.handleExpiryKey(e);
var r = o.expiryMonthYearInput.val();
1 == r.length &&
parseInt(r) > 1 &&
t.keyIsNumber(e) &&
o.expiryMonthYearInput.val(
t.applyFormatMask('0' + r, t.EXPIRY_MASK)
),
o.EXPIRY_USE_DROPDOWNS ||
null == o.expiryMonthYearInput ||
(o.expiryMonthInput.val(o.expiryMonth()),
o.expiryYearInput.val(
9 == r.length ? r.substr(5, 4) : null
));
}),
this.expiryMonthYearInput.blur(function (t) {
o.refreshExpiryMonthValidation();
}),
e.append(this.expiryMonthYearInput),
e.append(this.expiryMonthInput),
e.append(this.expiryYearInput);
}
r.append(e),
r.append("<div class='icon'></div>"),
r.find('.icon').append(t.CALENDAR_SVG);
}),
(t.prototype.setupCvcInput = function () {
this.stripe && this.cvcInput.attr('data-stripe', 'cvc'),
this.elem.append(
"<div class='cvc-container'><div class='cvc-wrapper'></div></div>"
);
var e = this.elem.find('.cvc-wrapper');
e.append(this.cvcInput),
e.append("<div class='icon'></div>"),
e.find('.icon').append(t.LOCK_SVG);
}),
(t.prototype.expiryMonth = function () {
if (
!this.EXPIRY_USE_DROPDOWNS &&
null != this.expiryMonthYearInput
) {
var t = this.expiryMonthYearInput.val();
return t.length >= 2 ? parseInt(t.substr(0, 2)) : null;
}
return null;
}),
(t.isValidMonth = function (t) {
return t >= 1 && 12 >= t;
}),
(t.isExpiryValid = function (e, r) {
var n = new Date(),
i = n.getMonth() + 1,
a = '' + n.getFullYear();
return (
2 == ('' + r).length && (r = a.substring(0, 2) + '' + r),
(i = parseInt(i)),
(a = parseInt(a)),
(e = parseInt(e)),
(r = parseInt(r)),
t.isValidMonth(e) && (r > a || (r == a && e >= i))
);
}),
(t.prototype.refreshExpiryMonthValidation = function () {
t.isExpiryValid(this.getExpiryMonth(), this.getExpiryYear())
? this.setExpiryMonthAsValid()
: this.setExpiryMonthAsInvalid();
}),
(t.prototype.setExpiryMonthAsValid = function () {
this.EXPIRY_USE_DROPDOWNS ||
this.expiryMonthYearInput.parent().removeClass('has-error');
}),
(t.prototype.setExpiryMonthAsInvalid = function () {
this.EXPIRY_USE_DROPDOWNS ||
this.expiryMonthYearInput.parent().addClass('has-error');
});
})();

View File

@ -0,0 +1,82 @@
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/*!********************************************************************!*\
!*** ./resources/js/clients/payments/forte-credit-card-payment.js ***!
\********************************************************************/
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
* 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://opensource.org/licenses/AAL
*/
var ForteAuthorizeCard = function ForteAuthorizeCard(apiLoginId) {
var _this = this;
_classCallCheck(this, ForteAuthorizeCard);
_defineProperty(this, "handleAuthorization", function () {
var myCard = $('#my-card');
var data = {
api_login_id: _this.apiLoginId,
card_number: myCard.CardJs('cardNumber').replace(/[^\d]/g, ''),
expire_year: myCard.CardJs('expiryYear').replace(/[^\d]/g, ''),
expire_month: myCard.CardJs('expiryMonth').replace(/[^\d]/g, ''),
cvv: document.getElementById('cvv').value.replace(/[^\d]/g, '')
};
var payNowButton = document.getElementById('pay-now');
if (payNowButton) {
document.getElementById('pay-now').disabled = true;
document.querySelector('#pay-now > svg').classList.remove('hidden');
document.querySelector('#pay-now > span').classList.add('hidden');
}
forte.createToken(data).success(_this.successResponseHandler).error(_this.failedResponseHandler);
return false;
});
_defineProperty(this, "successResponseHandler", function (response) {
document.getElementById('payment_token').value = response.onetime_token;
document.getElementById('card_brand').value = response.card_type;
document.getElementById('server_response').submit();
return false;
});
_defineProperty(this, "failedResponseHandler", function (response) {
var errors = '<div class="alert alert-failure mb-4"><ul><li>' + response.response_description + '</li></ul></div>';
document.getElementById('forte_errors').innerHTML = errors;
document.getElementById('pay-now').disabled = false;
document.querySelector('#pay-now > svg').classList.add('hidden');
document.querySelector('#pay-now > span').classList.remove('hidden');
return false;
});
_defineProperty(this, "handle", function () {
var payNowButton = document.getElementById('pay-now');
if (payNowButton) {
payNowButton.addEventListener('click', function (e) {
_this.handleAuthorization();
});
}
return _this;
});
this.apiLoginId = apiLoginId;
this.cardHolderName = document.getElementById('cardholder_name');
};
var apiLoginId = document.querySelector('meta[name="forte-api-login-id"]').content;
/** @handle */
new ForteAuthorizeCard(apiLoginId).handle();
/******/ })()
;

322468
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

317492
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

283989
public/main.next.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,81 @@
/**
* 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://opensource.org/licenses/AAL
*/
class ForteAuthorizeACH {
constructor(apiLoginId) {
this.apiLoginId = apiLoginId;
}
handleAuthorization = () => {
var account_number = document.getElementById('account-number').value;
var routing_number = document.getElementById('routing-number').value;
var data = {
api_login_id: this.apiLoginId,
account_number: account_number,
routing_number: routing_number,
account_type: 'checking',
};
let payNowButton = document.getElementById('pay-now');
if (payNowButton) {
document.getElementById('pay-now').disabled = true;
document.querySelector('#pay-now > svg').classList.remove('hidden');
document.querySelector('#pay-now > span').classList.add('hidden');
}
// console.log(data);
forte
.createToken(data)
.success(this.successResponseHandler)
.error(this.failedResponseHandler);
return false;
};
successResponseHandler = (response) => {
document.getElementById('payment_token').value = response.onetime_token;
document.getElementById('server_response').submit();
return false;
};
failedResponseHandler = (response) => {
var errors =
'<div class="alert alert-failure mb-4"><ul><li>' +
response.response_description +
'</li></ul></div>';
document.getElementById('forte_errors').innerHTML = errors;
document.getElementById('pay-now').disabled = false;
document.querySelector('#pay-now > svg').classList.add('hidden');
document.querySelector('#pay-now > span').classList.remove('hidden');
return false;
};
handle = () => {
let payNowButton = document.getElementById('pay-now');
if (payNowButton) {
payNowButton.addEventListener('click', (e) => {
this.handleAuthorization();
});
}
return this;
};
}
const apiLoginId = document.querySelector(
'meta[name="forte-api-login-id"]'
).content;
/** @handle */
new ForteAuthorizeACH(apiLoginId).handle();

View File

@ -0,0 +1,83 @@
/**
* 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://opensource.org/licenses/AAL
*/
class ForteAuthorizeCard {
constructor(apiLoginId) {
this.apiLoginId = apiLoginId;
this.cardHolderName = document.getElementById('cardholder_name');
}
handleAuthorization = () => {
var myCard = $('#my-card');
var data = {
api_login_id: this.apiLoginId,
card_number: myCard.CardJs('cardNumber').replace(/[^\d]/g, ''),
expire_year: myCard.CardJs('expiryYear').replace(/[^\d]/g, ''),
expire_month: myCard.CardJs('expiryMonth').replace(/[^\d]/g, ''),
cvv: document.getElementById('cvv').value.replace(/[^\d]/g, ''),
};
let payNowButton = document.getElementById('pay-now');
if (payNowButton) {
document.getElementById('pay-now').disabled = true;
document.querySelector('#pay-now > svg').classList.remove('hidden');
document.querySelector('#pay-now > span').classList.add('hidden');
}
forte
.createToken(data)
.success(this.successResponseHandler)
.error(this.failedResponseHandler);
return false;
};
successResponseHandler = (response) => {
document.getElementById('payment_token').value = response.onetime_token;
document.getElementById('card_brand').value = response.card_type;
document.getElementById('server_response').submit();
return false;
};
failedResponseHandler = (response) => {
var errors =
'<div class="alert alert-failure mb-4"><ul><li>' +
response.response_description +
'</li></ul></div>';
document.getElementById('forte_errors').innerHTML = errors;
document.getElementById('pay-now').disabled = false;
document.querySelector('#pay-now > svg').classList.add('hidden');
document.querySelector('#pay-now > span').classList.remove('hidden');
return false;
};
handle = () => {
let payNowButton = document.getElementById('pay-now');
if (payNowButton) {
payNowButton.addEventListener('click', (e) => {
this.handleAuthorization();
});
}
return this;
};
}
const apiLoginId = document.querySelector(
'meta[name="forte-api-login-id"]'
).content;
/** @handle */
new ForteAuthorizeCard(apiLoginId).handle();

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-user-agent="{{ $user_agent }}" data-login="{{ $login }}">
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-user-agent="{{ $user_agent }}" data-login="{{ $login }}" data-signup="{{ $signup }}">
<head>
<!-- Source: https://github.com/invoiceninja/invoiceninja -->
<!-- Version: {{ config('ninja.app_version') }} -->
@ -152,6 +152,12 @@
<script defer src="{{ $path }}?v={{ config('ninja.app_version') }}" type="application/javascript"></script>
<script type="text/javascript"
src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js"
integrity="sha384-ggh+EF1aSqm+Y4yvv2n17KpurNcZTeYtUZUvhPziElsstmIEubyEB6AIVpKLuZgr"
crossorigin="anonymous">
</script>
<center style="padding-top: 150px" id="loader">
<div class="loader"></div>
</center>

View File

@ -278,6 +278,13 @@
margin-top: 1rem;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/

View File

@ -270,6 +270,12 @@
bottom: 0;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/

View File

@ -240,6 +240,12 @@
bottom: 0;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/
@ -335,7 +341,7 @@ $entity_images
'product-table', 'task-table', 'delivery-note-table',
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
'client-details','vendor-details'
'client-details','vendor-details', 'swiss-qr'
];
tables.forEach((tableIdentifier) => {

View File

@ -222,6 +222,12 @@
position: fixed;
bottom: 0;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/

View File

@ -228,6 +228,12 @@
bottom: 0;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/

View File

@ -245,6 +245,12 @@
bottom: 0;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/

View File

@ -272,6 +272,12 @@
white-space: nowrap;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/

View File

@ -215,6 +215,12 @@
bottom: 0;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/

View File

@ -288,6 +288,12 @@
bottom: 0;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/

View File

@ -252,6 +252,12 @@
bottom: 0;
}
[data-element='product_table-product.description-td'], td {
min-width:100%;
max-width: 300px;
overflow-wrap: break-word;
}
/** Useful snippets, uncomment to enable. **/
/** Hide company logo **/

View File

@ -0,0 +1,131 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Bank Details', 'card_title' => 'Bank Details'])
@section('gateway_head')
@if($gateway->getConfigField('testMode'))
<script type="text/javascript" src="https://sandbox.forte.net/api/js/v1"></script>
@else
<script type="text/javascript" src="https://api.forte.net/js/v1"></script>
@endif
@endsection
@section('gateway_content')
@if(session()->has('ach_error'))
<div class="alert alert-failure mb-4">
<p>{{ session('ach_error') }}</p>
</div>
@endif
@if(Session::has('error'))
<div class="alert alert-failure mb-4" id="errors">{{ Session::get('error') }}</div>
@endif
<div id="forte_errors"></div>
@if ($errors->any())
<div class="alert alert-failure mb-4">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::BANK_TRANSFER]) }}" method="post" id="server_response">
@csrf
<input type="hidden" name="gateway_type_id" value="2">
<input type="hidden" name="gateway_response" id="gateway_response">
<input type="hidden" name="is_default" id="is_default">
<input type="hidden" name="last_4" id="last_4">
<input type="hidden" name="one_time_token" id="one_time_token">
<div class="alert alert-failure mb-4" hidden id="errors"></div>
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_type')])
<span class="flex items-center mr-4">
<input class="form-radio mr-2" type="radio" value="individual" name="account-holder-type" checked>
<span>{{ __('texts.individual_account') }}</span>
</span>
<span class="flex items-center">
<input class="form-radio mr-2" type="radio" value="company" name="account-holder-type">
<span>{{ __('texts.company_account') }}</span>
</span>
@endcomponent
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_holder_name')])
<input class="input w-full" id="account-holder-name" type="text" name="account_holder_name" placeholder="{{ ctrans('texts.name') }}" required>
@endcomponent
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.country')])
<select name="countries" id="country" name="country" class="form-select input w-full" required>
@foreach($countries as $country)
<option value="{{ $country->iso_3166_2 }}" {{$country->iso_3166_2 == 'US' ? "selected" : ""}}>{{ $country->iso_3166_2 }} ({{ $country->name }})</option>
@endforeach
</select>
@endcomponent
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.currency')])
<select name="currencies" id="currency" name="currency" class="form-select input w-full">
@foreach($currencies as $currency)
<option value="{{ $currency->code }}" {{$currency->code == 'USD' ? "selected" : ""}}>{{ $currency->code }} ({{ $currency->name }})</option>
@endforeach
</select>
@endcomponent
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.routing_number')])
<input class="input w-full" id="routing-number" type="text" required>
@endcomponent
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.account_number')])
<input class="input w-full" id="account-number" type="text" required>
@endcomponent
@component('portal.ninja2020.components.general.card-element-single')
<input type="checkbox" class="form-checkbox mr-1" name="accept_terms" id="accept-terms" required>
<label for="accept-terms" class="cursor-pointer">{{ ctrans('texts.ach_authorization', ['company' => auth()->user()->company->present()->name, 'email' => auth('contact')->user()->client->company->settings->email]) }}</label>
@endcomponent
<div class="bg-white px-4 py-5 flex justify-end">
<button type="button"
onclick="submitACH()"
class="button button-primary bg-primary {{ $class ?? '' }}">
<svg class="animate-spin h-5 w-5 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>{{ $slot ?? ctrans('texts.add_payment_method') }}</span>
</button>
<input type="submit" style="display: none" id="form_btn">
</div>
</form>
@endsection
@section('gateway_footer')
<script>
function onTokenCreated(params) {
document.getElementById('one_time_token').value=params.onetime_token;
document.getElementById('last_4').value=params.last_4;
let button = document.querySelector("#form_btn");
button.click();
}
function onTokenFailed(params) {
var errors = '<div class="alert alert-failure mb-4"><ul><li>'+ params.response_description +'</li></ul></div>';
document.getElementById("forte_errors").innerHTML = errors;
}
function submitACH(){
var account_number=document.getElementById('account-number').value;
var routing_number=document.getElementById('routing-number').value;
var data = {
api_login_id: '{{$gateway->getConfigField("apiLoginId")}}',
account_number: account_number,
routing_number: routing_number,
account_type: "checking",
}
forte.createToken(data)
.success(onTokenCreated)
.error(onTokenFailed);
return false;
}
</script>
@endsection

View File

@ -0,0 +1,53 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Bank Transfer', 'card_title' => 'Bank Transfer'])
@section('gateway_head')
<meta name="forte-api-login-id" content="{{$gateway->forte->company_gateway->getConfigField("apiLoginId")}}">
@endsection
@section('gateway_content')
<form action="{{ route('client.payments.response') }}" method="post" id="server_response">
@csrf
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="company_gateway_id" value="{{ $gateway->forte->company_gateway->id }}">
<input type="hidden" name="payment_method_id" value="{{$payment_method_id}}">
<input type="hidden" name="gateway_response" id="gateway_response">
<input type="hidden" name="dataValue" id="dataValue"/>
<input type="hidden" name="dataDescriptor" id="dataDescriptor"/>
<input type="hidden" name="token" id="token"/>
<input type="hidden" name="store_card" id="store_card"/>
<input type="submit" style="display: none" id="form_btn">
</form>
<div id="forte_errors"></div>
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
Bank Transfer
@endcomponent
@include('portal.ninja2020.gateways.includes.payment_details')
@component('portal.ninja2020.components.general.card-element', ['title' => 'Pay with Bank Transfer'])
<input type="hidden" name="payment_token" id="payment_token">
<div class="bg-white px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
style="display: flex!important; justify-content: center!important;">
<input class="input w-full" id="routing-number" type="text" placeholder="{{ctrans('texts.routing_number')}}" required>
</div>
<div class="bg-white px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
style="display: flex!important; justify-content: center!important;">
<input class="input w-full" id="account-number" type="text" placeholder="{{ctrans('texts.account_number')}}" required>
</div>
@endcomponent
@include('portal.ninja2020.gateways.includes.pay_now')
@endsection
@section('gateway_footer')
@if($gateway->forte->company_gateway->getConfigField('testMode'))
<script type="text/javascript" src="https://sandbox.forte.net/api/js/v1"></script>
@else
<script type="text/javascript" src="https://api.forte.net/js/v1"></script>
@endif
<script src="{{ asset('js/clients/payments/forte-ach-payment.js') }}"></script>
@endsection

View File

@ -0,0 +1,122 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')])
@section('gateway_head')
<meta name="year-invalid" content="{{ ctrans('texts.year_invalid') }}">
<meta name="month-invalid" content="{{ ctrans('texts.month_invalid') }}">
<meta name="credit-card-invalid" content="{{ ctrans('texts.credit_card_invalid') }}">
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="{{ asset('js/clients/payments/forte-card-js.min.js') }}"></script>
<link href="{{ asset('css/card-js.min.css') }}" rel="stylesheet" type="text/css">
@if($gateway->getConfigField('testMode'))
<script type="text/javascript" src="https://sandbox.forte.net/api/js/v1"></script>
@else
<script type="text/javascript" src="https://api.forte.net/js/v1"></script>
@endif
@endsection
@section('gateway_content')
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}"
method="post" id="server_response">
@csrf
<input type="hidden" name="payment_method_id" value="1">
<input type="hidden" name="one_time_token" id="one_time_token">
<input type="hidden" name="card_type" id="card_type">
<input type="hidden" name="expire_year" id="expire_year">
<input type="hidden" name="expire_month" id="expire_month">
<input type="hidden" name="last_4" id="last_4">
@if(!Request::isSecure())
<p class="alert alert-failure">{{ ctrans('texts.https_required') }}</p>
@endif
@if(Session::has('error'))
<div class="alert alert-failure mb-4" id="errors">{{ Session::get('error') }}</div>
@endif
<div id="forte_errors"></div>
@if ($errors->any())
<div class="alert alert-failure mb-4">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')])
{{ ctrans('texts.credit_card') }}
@endcomponent
@include('portal.ninja2020.gateways.forte.includes.credit_card')
<div class="bg-white px-4 py-5 flex justify-end">
<button type="button"
onclick="submitCard()"
class="button button-primary bg-primary {{ $class ?? '' }}">
<svg class="animate-spin h-5 w-5 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>{{ $slot ?? ctrans('texts.add_payment_method') }}</span>
</button>
<input type="submit" style="display: none" id="form_btn">
</div>
</form>
@endsection
@section('gateway_footer')
<script>
function onTokenCreated(params) {
document.getElementById('one_time_token').value=params.onetime_token;
document.getElementById('last_4').value=params.last_4;
let button = document.querySelector("#form_btn");
button.click();
}
function onTokenFailed(params) {
var errors = '<div class="alert alert-failure mb-4"><ul><li>'+ params.response_description +'</li></ul></div>';
document.getElementById("forte_errors").innerHTML = errors;
}
function submitCard(){
var doc = document.getElementsByClassName("card-number-wrapper");
var cardType=doc[0].childNodes[1].classList[2];
if (cardType=='master-card') {
document.getElementById('card_type').value='mast';
} else if(cardType=='visa') {
document.getElementById('card_type').value='visa';
}else if(cardType=='jcb') {
document.getElementById('card_type').value='jcb';
}else if(cardType=='discover') {
document.getElementById('card_type').value='disc';
}else if(cardType=='american-express') {
document.getElementById('card_type').value='amex';
}else{
document.getElementById('card_type').value=cardType;
}
var month=document.querySelector('input[name=expiry-month]').value;
var year=document.querySelector('input[name=expiry-year]').value;
var cc=document.getElementById('card_number').value.replaceAll(' ','');
var cvv=document.getElementById('cvv').value;
document.getElementById('expire_year').value=year;
document.getElementById('expire_month').value=month;
var data = {
api_login_id: '{{$gateway->getConfigField("apiLoginId")}}',
card_number: cc,
expire_year: year,
expire_month: month,
cvv: cvv,
}
forte.createToken(data)
.success(onTokenCreated)
.error(onTokenFailed);
return false;
}
</script>
@endsection

View File

@ -0,0 +1,51 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' => ctrans('texts.payment_type_credit_card')])
@section('gateway_head')
<meta name="forte-api-login-id" content="{{$gateway->forte->company_gateway->getConfigField("apiLoginId")}}">
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="{{ asset('js/clients/payments/forte-card-js.min.js') }}"></script>
<link href="{{ asset('css/card-js.min.css') }}" rel="stylesheet" type="text/css">
@endsection
@section('gateway_content')
<form action="{{ route('client.payments.response') }}" method="post" id="server_response">
@csrf
<input type="hidden" name="card_brand" id="card_brand">
<input type="hidden" name="payment_token" id="payment_token">
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="company_gateway_id" value="{{ $gateway->forte->company_gateway->id }}">
<input type="hidden" name="payment_method_id" value="{{$payment_method_id}}">
<input type="hidden" name="gateway_response" id="gateway_response">
<input type="hidden" name="dataValue" id="dataValue"/>
<input type="hidden" name="dataDescriptor" id="dataDescriptor"/>
<input type="hidden" name="token" id="token"/>
<input type="hidden" name="store_card" id="store_card"/>
<input type="submit" style="display: none" id="form_btn">
</form>
<div id="forte_errors"></div>
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
{{ ctrans('texts.credit_card') }}
@endcomponent
@include('portal.ninja2020.gateways.includes.payment_details')
@component('portal.ninja2020.components.general.card-element', ['title' => 'Pay with Credit Card'])
@include('portal.ninja2020.gateways.forte.includes.credit_card')
@endcomponent
@include('portal.ninja2020.gateways.includes.pay_now')
@endsection
@section('gateway_footer')
@if($gateway->forte->company_gateway->getConfigField('testMode'))
<script type="text/javascript" src="https://sandbox.forte.net/api/js/v1"></script>
@else
<script type="text/javascript" src="https://api.forte.net/js/v1"></script>
@endif
<script src="{{ asset('js/clients/payments/forte-credit-card-payment.js') }}"></script>
@endsection

View File

@ -0,0 +1,12 @@
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
style="display: flex!important; justify-content: center!important;" id="authorize--credit-card-container">
<div class="card-js" id="my-card" data-capture-name="true">
<input class="name" id="cardholder_name" name="card_holders_name" placeholder="{{ ctrans('texts.name')}}">
<input class="card-number my-custom-class" id="card_number">
<input type="hidden" name="expiry_month" id="expiration_month">
<input type="hidden" name="expiry_year" id="expiration_year">
<input class="cvc" name="cvc" id="cvv">
</div>
<div id="errors"></div>
</div>

View File

@ -3,7 +3,7 @@
@push('head')
<meta name="show-purchase_order-terms" content="false">
<meta name="require-purchase_order-signature" content="{{ $purchase_order->company->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_purchase_order_signature }}">
<meta name="require-purchase_order-signature" content="{{ $purchase_order->company->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && property_exists($settings, 'require_purchase_order_signature') && $settings->require_purchase_order_signature }}">
@include('portal.ninja2020.components.no-cache')
<script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script>

View File

@ -129,6 +129,9 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
Route::post('preview', 'PreviewController@show')->name('preview.show');
Route::post('live_preview', 'PreviewController@live')->name('preview.live');
Route::post('preview/purchase_order', 'PreviewPurchaseOrderController@show')->name('preview_purchase_order.show');
Route::post('live_preview/purchase_order', 'PreviewPurchaseOrderController@live')->name('preview_purchase_order.live');
Route::resource('products', 'ProductController'); // name = (products. index / create / show / update / destroy / edit
Route::post('products/bulk', 'ProductController@bulk')->name('products.bulk');
Route::put('products/{product}/upload', 'ProductController@upload');
@ -212,6 +215,7 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
Route::get('purchase_orders/{purchase_order}/{action}', 'PurchaseOrderController@action')->name('purchase_orders.action');
Route::get('users', 'UserController@index');
Route::get('users/create', 'UserController@create')->middleware('password_protected');
Route::get('users/{user}', 'UserController@show')->middleware('password_protected');
Route::put('users/{user}', 'UserController@update')->middleware('password_protected');
Route::post('users', 'UserController@store')->middleware('password_protected');

View File

@ -20,6 +20,8 @@ Route::get('vendors', [VendorContactLoginController::class, 'catch'])->name('ven
Route::group(['middleware' => ['invite_db'], 'prefix' => 'vendor', 'as' => 'vendor.'], function () {
/*Invitation catches*/
Route::get('purchase_order/{invitation_key}', [InvitationController::class, 'purchaseOrder']);
Route::get('purchase_order/{invitation_key}/download', [InvitationController::class, 'download']);
// Route::get('purchase_order/{invitation_key}/download_pdf', 'PurchaseOrderController@downloadPdf')->name('recurring_invoice.download_invitation_key');
// Route::get('purchase_order/{invitation_key}/download', 'ClientPortal\InvitationController@routerForDownload');
@ -40,4 +42,7 @@ Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'pr
});
Route::fallback('BaseController@notFoundVendor');

View File

@ -56,7 +56,7 @@ class CompanySettingsTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
}
@ -78,11 +78,13 @@ class CompanySettingsTest extends TestCase
$this->company->saveSettings($settings, $this->company);
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
nlog($message);
@ -109,7 +111,7 @@ class CompanySettingsTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
$response->assertStatus(200);
@ -135,7 +137,7 @@ class CompanySettingsTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
$response->assertStatus(200);
@ -162,7 +164,7 @@ class CompanySettingsTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
$response->assertStatus(200);
@ -185,7 +187,7 @@ class CompanySettingsTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->post('/api/v1/companies?include=company', $this->company->toArray());
])->postJson('/api/v1/companies?include=company', $this->company->toArray());
$arr = $response->json();
$response->assertStatus(200);
@ -203,7 +205,7 @@ class CompanySettingsTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->post('/api/v1/companies?include=company', $this->company->toArray());
])->postJson('/api/v1/companies?include=company', $this->company->toArray());
$arr = $response->json();
$response->assertStatus(200);
@ -221,7 +223,7 @@ class CompanySettingsTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->post('/api/v1/companies?include=company', $this->company->toArray());
])->postJson('/api/v1/companies?include=company', $this->company->toArray());
$arr = $response->json();
$response->assertStatus(200);
@ -239,7 +241,7 @@ class CompanySettingsTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-Token' => $this->token,
])->post('/api/v1/companies?include=company', $this->company->toArray());
])->postJson('/api/v1/companies?include=company', $this->company->toArray());
$arr = $response->json();
$response->assertStatus(200);

View File

@ -47,6 +47,31 @@ class PreviewTest extends TestCase
$response->assertStatus(200);
}
public function testPurchaseOrderPreviewRoute()
{
$data = $this->getData();
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/preview/purchase_order', $data);
$response->assertStatus(200);
}
public function testPurchaseOrderPreviewHtmlResponse()
{
$data = $this->getData();
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/preview/purchase_order?html=true', $data);
$response->assertStatus(200);
}
public function testPreviewHtmlResponse()
{
$data = $this->getData();

View File

@ -124,7 +124,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses/', $data)
])->postJson('/api/v1/expenses/', $data)
->assertStatus(200);
@ -137,7 +137,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/expenses/' . $arr['data']['id'], $data)
])->putJson('/api/v1/expenses/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -148,19 +148,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses/bulk?action=archive', $data)
])->postJson('/api/v1/expenses/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses/bulk?action=restore', $data)
])->postJson('/api/v1/expenses/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses/bulk?action=delete', $data)
])->postJson('/api/v1/expenses/bulk?action=delete', $data)
->assertStatus(200);
}
@ -183,7 +183,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/vendors/', $data)
])->postJson('/api/v1/vendors/', $data)
->assertStatus(200);
@ -197,7 +197,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/vendors/' . $arr['data']['id'], $data)
])->putJson('/api/v1/vendors/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -208,19 +208,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/vendors/bulk?action=archive', $data)
])->postJson('/api/v1/vendors/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/vendors/bulk?action=restore', $data)
])->postJson('/api/v1/vendors/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/vendors/bulk?action=delete', $data)
])->postJson('/api/v1/vendors/bulk?action=delete', $data)
->assertStatus(200);
}
@ -245,7 +245,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/tasks/', $data)
])->postJson('/api/v1/tasks/', $data)
->assertStatus(200);
@ -259,7 +259,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/tasks/' . $arr['data']['id'], $data)
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -270,19 +270,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/tasks/bulk?action=archive', $data)
])->postJson('/api/v1/tasks/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/tasks/bulk?action=restore', $data)
])->postJson('/api/v1/tasks/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/tasks/bulk?action=delete', $data)
])->postJson('/api/v1/tasks/bulk?action=delete', $data)
->assertStatus(200);
}
@ -306,7 +306,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/credits/', $data)
])->postJson('/api/v1/credits/', $data)
->assertStatus(200);
@ -320,7 +320,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/credits/' . $arr['data']['id'], $data)
])->putJson('/api/v1/credits/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -331,19 +331,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/credits/bulk?action=archive', $data)
])->postJson('/api/v1/credits/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/credits/bulk?action=restore', $data)
])->postJson('/api/v1/credits/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/credits/bulk?action=delete', $data)
])->postJson('/api/v1/credits/bulk?action=delete', $data)
->assertStatus(200);
}
@ -368,7 +368,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/quotes/', $data)
])->postJson('/api/v1/quotes/', $data)
->assertStatus(200);
@ -382,7 +382,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/quotes/' . $arr['data']['id'], $data)
])->putJson('/api/v1/quotes/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -397,25 +397,25 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/quotes/bulk?action=archive', $data)
])->postJson('/api/v1/quotes/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/quotes/bulk?action=restore', $data)
])->postJson('/api/v1/quotes/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/quotes/bulk?action=approve', $data)
])->postJson('/api/v1/quotes/bulk?action=approve', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/quotes/bulk?action=delete', $data)
])->postJson('/api/v1/quotes/bulk?action=delete', $data)
->assertStatus(200);
}
@ -449,7 +449,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments?include=invoices', $data)
])->postJson('/api/v1/payments?include=invoices', $data)
->assertStatus(200);
$arr = $response->json();
@ -461,7 +461,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/payments/' . $arr['data']['id'], $data)
])->putJson('/api/v1/payments/' . $arr['data']['id'], $data)
->assertStatus(200);
$data = [
@ -471,19 +471,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments/bulk?action=archive', $data)
])->postJson('/api/v1/payments/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments/bulk?action=restore', $data)
])->postJson('/api/v1/payments/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments/bulk?action=delete', $data)
])->postJson('/api/v1/payments/bulk?action=delete', $data)
->assertStatus(200);
}
@ -507,7 +507,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/', $data)
])->postJson('/api/v1/invoices/', $data)
->assertStatus(200);
@ -521,7 +521,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/invoices/' . $arr['data']['id'], $data)
])->putJson('/api/v1/invoices/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -532,19 +532,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/bulk?action=archive', $data)
])->postJson('/api/v1/invoices/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/bulk?action=restore', $data)
])->postJson('/api/v1/invoices/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/bulk?action=delete', $data)
])->postJson('/api/v1/invoices/bulk?action=delete', $data)
->assertStatus(200);
}
@ -571,7 +571,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/recurring_invoices/', $data);
])->postJson('/api/v1/recurring_invoices/', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
}
@ -590,7 +590,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/recurring_invoices/' . $arr['data']['id'], $data)
])->putJson('/api/v1/recurring_invoices/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -601,19 +601,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/recurring_invoices/bulk?action=archive', $data)
])->postJson('/api/v1/recurring_invoices/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/recurring_invoices/bulk?action=restore', $data)
])->postJson('/api/v1/recurring_invoices/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/recurring_invoices/bulk?action=delete', $data)
])->postJson('/api/v1/recurring_invoices/bulk?action=delete', $data)
->assertStatus(200);
}
@ -636,7 +636,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/clients/', $data)
])->postJson('/api/v1/clients/', $data)
->assertStatus(200);
@ -650,7 +650,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/clients/' . $arr['data']['id'], $data)
])->putJson('/api/v1/clients/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -661,19 +661,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/clients/bulk?action=archive', $data)
])->postJson('/api/v1/clients/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/clients/bulk?action=restore', $data)
])->postJson('/api/v1/clients/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/clients/bulk?action=delete', $data)
])->postJson('/api/v1/clients/bulk?action=delete', $data)
->assertStatus(200);
}
@ -705,7 +705,7 @@ class EventTest extends TestCase
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->post('/api/v1/users?include=company_user', $data)
])->postJson('/api/v1/users?include=company_user', $data)
->assertStatus(200);
$arr = $response->json();
@ -725,7 +725,7 @@ class EventTest extends TestCase
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->put('/api/v1/users/' . $arr['data']['id'], $data)
])->putJson('/api/v1/users/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -737,21 +737,21 @@ class EventTest extends TestCase
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->post('/api/v1/users/bulk?action=archive', $data)
])->postJson('/api/v1/users/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->post('/api/v1/users/bulk?action=restore', $data)
])->postJson('/api/v1/users/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->post('/api/v1/users/bulk?action=delete', $data)
])->postJson('/api/v1/users/bulk?action=delete', $data)
->assertStatus(200);
}
@ -772,7 +772,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/subscriptions/', $data)
])->postJson('/api/v1/subscriptions/', $data)
->assertStatus(200);
@ -785,7 +785,7 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/subscriptions/' . $arr['data']['id'], $data)
])->putJson('/api/v1/subscriptions/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -796,19 +796,19 @@ class EventTest extends TestCase
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/subscriptions/bulk?action=archive', $data)
])->postJson('/api/v1/subscriptions/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/subscriptions/bulk?action=restore', $data)
])->postJson('/api/v1/subscriptions/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/subscriptions/bulk?action=delete', $data)
])->postJson('/api/v1/subscriptions/bulk?action=delete', $data)
->assertStatus(200);
}
@ -833,7 +833,7 @@ public function PurchaseOrderEvents()
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/purchase_orders/', $data)
])->postJson('/api/v1/purchase_orders/', $data)
->assertStatus(200);
@ -847,7 +847,7 @@ public function PurchaseOrderEvents()
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/purchase_orders/' . $arr['data']['id'], $data)
])->putJson('/api/v1/purchase_orders/' . $arr['data']['id'], $data)
->assertStatus(200);
@ -862,25 +862,25 @@ public function PurchaseOrderEvents()
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/purchase_orders/bulk?action=archive', $data)
])->postJson('/api/v1/purchase_orders/bulk?action=archive', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/purchase_orders/bulk?action=restore', $data)
])->postJson('/api/v1/purchase_orders/bulk?action=restore', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/purchase_orders/bulk?action=approve', $data)
])->postJson('/api/v1/purchase_orders/bulk?action=approve', $data)
->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/purchase_orders/bulk?action=delete', $data)
])->postJson('/api/v1/purchase_orders/bulk?action=delete', $data)
->assertStatus(200);
}

8
webpack.mix.js vendored
View File

@ -10,6 +10,14 @@ mix.js("resources/js/app.js", "public/js")
"resources/js/clients/payments/authorize-credit-card-payment.js",
"public/js/clients/payments/authorize-credit-card-payment.js"
)
.js(
"resources/js/clients/payments/forte-credit-card-payment.js",
"public/js/clients/payments/forte-credit-card-payment.js"
)
.js(
"resources/js/clients/payments/forte-ach-payment.js",
"public/js/clients/payments/forte-ach-payment.js"
)
.js(
"resources/js/clients/payments/stripe-ach.js",
"public/js/clients/payments/stripe-ach.js"