1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-11 13:42:49 +01:00

Merge pull request #7097 from turbo124/v5-stable

v5.3.42
This commit is contained in:
David Bomba 2022-01-08 15:37:19 +11:00 committed by GitHub
commit 7791110bac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 847 additions and 65 deletions

View File

@ -1 +1 @@
5.3.41
5.3.42

View File

@ -0,0 +1,103 @@
<?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\Http\Controllers\ClientPortal;
use App\Http\Controllers\Controller;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Utils\Ninja;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class ApplePayDomainController extends Controller
{
private array $stripe_keys = ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23'];
public function showAppleMerchantId(Request $request)
{
/* Self Host */
if(Ninja::isSelfHost()){
$cgs = CompanyGateway::whereIn('gateway_key', $this->stripe_keys)
->where('is_deleted', false)
->get();
foreach($cgs as $cg)
{
if($cg->getConfigField('appleDomainVerification')){
return response($cg->getConfigField('appleDomainVerification'),200);
}
}
return response('', 400);
}
/* Hosted */
$domain_name = $request->getHost();
if (strpos($domain_name, 'invoicing.co') !== false)
{
$subdomain = explode('.', $domain_name)[0];
$query = [
'subdomain' => $subdomain,
'portal_mode' => 'subdomain',
];
if($company = MultiDB::findAndSetDbByDomain($query)){
return $this->resolveAppleMerchantId($company);
}
}
$query = [
'portal_domain' => $request->getSchemeAndHttpHost(),
'portal_mode' => 'domain',
];
if($company = MultiDB::findAndSetDbByDomain($query)){
return $this->resolveAppleMerchantId($company);
}
return response('', 400);
}
private function resolveAppleMerchantId($company)
{
$cgs = $company->company_gateways()
->whereIn('gateway_key', $this->stripe_keys)
->where('is_deleted', false)
->get();
foreach($cgs as $cg)
{
if($cg->getConfigField('appleDomainVerification')){
return response($cg->getConfigField('appleDomainVerification'),200);
}
}
return response('', 400);
}
}

View File

@ -1,4 +1,13 @@
<?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\Http\Controllers\ClientPortal;

View File

@ -31,6 +31,7 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
class QuoteController extends Controller
{
@ -54,7 +55,9 @@ class QuoteController extends Controller
* @return Factory|View|BinaryFileResponse
*/
public function show(ShowQuoteRequest $request, Quote $quote)
{
{
/* If the quote is expired, convert the status here */
$data = [
'quote' => $quote,
];

View File

@ -20,6 +20,7 @@ use App\Http\Requests\CompanyGateway\EditCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\ShowCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\StoreCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest;
use App\Jobs\Util\ApplePayDomain;
use App\Models\Client;
use App\Models\CompanyGateway;
use App\Repositories\CompanyRepository;
@ -45,6 +46,9 @@ class CompanyGatewayController extends BaseController
public $forced_includes = [];
private array $stripe_keys = ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23'];
/**
* CompanyGatewayController constructor.
* @param CompanyRepository $company_repo
@ -206,6 +210,8 @@ class CompanyGatewayController extends BaseController
$company_gateway->save();
}
ApplePayDomain::dispatch($company_gateway, $company_gateway->company->db);
return $this->itemResponse($company_gateway);
}
@ -379,6 +385,8 @@ class CompanyGatewayController extends BaseController
$company_gateway->save();
ApplePayDomain::dispatch($company_gateway, $company_gateway->company->db);
return $this->itemResponse($company_gateway);
}

View File

@ -304,6 +304,7 @@ class MigrationController extends BaseController
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($user->account->companies()->first()->settings));
App::setLocale($user->account->companies()->first()->getLocale());
if(!$existing_company && $company_count >=10) {

View File

@ -20,6 +20,8 @@ use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Models\SystemLog;
use App\Notifications\Ninja\EmailBounceNotification;
use App\Notifications\Ninja\EmailSpamNotification;
use Illuminate\Http\Request;
use Turbo124\Beacon\Facades\LightLogs;
@ -173,6 +175,10 @@ class PostMarkController extends BaseController
LightLogs::create($bounce)->queue();
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
if(config('ninja.notification.slack'))
$this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja();
}
// {
@ -215,6 +221,10 @@ class PostMarkController extends BaseController
LightLogs::create($spam)->queue();
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
if(config('ninja.notification.slack'))
$this->invitation->company->notification(new EmailSpamNotification($this->invitation->company->account))->ninja();
}
private function discoverInvitation($message_id)

View File

@ -21,6 +21,7 @@ use App\Http\Requests\Token\StoreTokenRequest;
use App\Http\Requests\Token\UpdateTokenRequest;
use App\Models\CompanyToken;
use App\Repositories\TokenRepository;
use App\Transformers\CompanyTokenHashedTransformer;
use App\Transformers\CompanyTokenTransformer;
use App\Utils\Traits\ChecksEntityStatus;
use App\Utils\Traits\MakesHash;
@ -93,6 +94,8 @@ class TokenController extends BaseController
*/
public function index(TokenFilters $filters)
{
$this->entity_transformer = CompanyTokenHashedTransformer::class;
$tokens = CompanyToken::filter($filters);
return $this->listResponse($tokens);
@ -205,6 +208,8 @@ class TokenController extends BaseController
*/
public function edit(EditTokenRequest $request, CompanyToken $token)
{
$this->entity_transformer = CompanyTokenHashedTransformer::class;
return $this->itemResponse($token);
}
@ -265,6 +270,8 @@ class TokenController extends BaseController
return $request->disallowUpdate();
}
$this->entity_transformer = CompanyTokenHashedTransformer::class;
$token = $this->token_repo->save($request->all(), $token);
return $this->itemResponse($token->fresh());
@ -419,6 +426,8 @@ class TokenController extends BaseController
//may not need these destroy routes as we are using actions to 'archive/delete'
$token->delete();
$this->entity_transformer = CompanyTokenHashedTransformer::class;
return $this->itemResponse($token);
}
@ -475,6 +484,9 @@ class TokenController extends BaseController
*/
public function bulk()
{
$this->entity_transformer = CompanyTokenHashedTransformer::class;
$action = request()->input('action');
$ids = request()->input('ids');

View File

@ -42,13 +42,45 @@ class QuotesTable extends Component
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc');
if (count($this->status) > 0) {
$query = $query->whereIn('status_id', $this->status);
/* Special filter for expired*/
if(in_array("-1", $this->status)){
// $query->whereDate('due_date', '<=', now()->startOfDay());
$query->where(function ($query){
$query->whereDate('due_date', '<=', now()->startOfDay())
->whereNotNull('due_date')
->where('status_id', '<>', Quote::STATUS_CONVERTED);
});
}
if(in_array("2", $this->status)){
$query->where(function ($query){
$query->whereDate('due_date', '>=', now()->startOfDay())
->orWhereNull('due_date');
})->where('status_id', Quote::STATUS_SENT);
}
if(in_array("3", $this->status)){
$query->whereIn('status_id', [Quote::STATUS_APPROVED, Quote::STATUS_CONVERTED]);
}
}
$query = $query
->where('company_id', $this->company->id)
->where('client_id', auth('contact')->user()->client->id)
->where('status_id', '<>', Quote::STATUS_DRAFT)
// ->where(function ($query){
// $query->whereDate('due_date', '>=', now())
// ->orWhereNull('due_date');
// })
->where('is_deleted', 0)
->withTrashed()
->paginate($this->per_page);

View File

@ -47,7 +47,7 @@ class StoreInvoiceRequest extends Request
$rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
}
$rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
// $rules['client_id'] = ['required', Rule::exists('clients')->where('company_id', auth()->user()->company()->id)];
$rules['invitations.*.client_contact_id'] = 'distinct';

View File

@ -335,11 +335,12 @@ class CompanyImport implements ShouldQueue
}
}
if($this->company->account->isFreeHostedClient() && $client_count = count($this->getObject('clients', true)) > config('ninja.quotas.free.clients')){
if($this->company->account->isFreeHostedClient() && (count($this->getObject('clients', true)) > config('ninja.quotas.free.clients')) ){
nlog("client quota busted");
$client_limit = config('ninja.quotas.free.clients');
$client_count = count($this->getObject('clients', true));
$this->message = "You are attempting to import ({$client_count}) clients, your current plan allows a total of ({$client_limit})";

View File

@ -135,7 +135,7 @@ class SendRecurring implements ShouldQueue
if ($invitation->contact && !$invitation->contact->trashed() && strlen($invitation->contact->email) >=1 && $invoice->client->getSetting('auto_email_invoice')) {
try{
EmailEntity::dispatch($invitation, $invoice->company)->delay(now()->addSeconds(1));
EmailEntity::dispatch($invitation, $invoice->company);
}
catch(\Exception $e) {
nlog($e->getMessage());

View File

@ -0,0 +1,100 @@
<?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\Jobs\Util;
use App\Jobs\Util\UnlinkFile;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\CompanyGateway;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
class ApplePayDomain implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private CompanyGateway $company_gateway;
private string $db;
private array $stripe_keys = ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23'];
public $tries = 1;
public function __construct(CompanyGateway $company_gateway, string $db)
{
$this->db = $db;
$this->company_gateway = $company_gateway;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
MultiDB::setDB($this->db);
if(in_array($this->company_gateway->gateway_key, $this->stripe_keys))
{
$domain = $this->getDomain();
try{
$this->company_gateway->driver()->setApplePayDomain($domain);
}
catch(\Exception $e){
nlog("failed to set Apple Domain with Stripe " . $e->getMessage());
}
}
}
private function getDomain()
{
$domain = '';
if(Ninja::isHosted())
{
if($this->company->portal_mode == 'domain'){
$domain = $this->company->portal_domain;
}
else{
$domain = $this->company->subdomain . '.' . config('ninja.app_domain');
}
}
else {
$domain = config('ninja.app_url');
}
$parsed_url = parse_url($domain);
return $parsed_url['host'];
}
}

View File

@ -471,8 +471,9 @@ class Import implements ShouldQueue
}
if ($key == 'payment_terms' && $key = '') {
$value = -1;
/* changes $key = '' to $value == '' and changed the return value from -1 to "0" 06/01/2022 */
if ($key == 'payment_terms' && $value == '') {
$value = "0";
}
$company_settings->{$key} = $value;

View File

@ -343,7 +343,10 @@ class CompanyGateway extends BaseModel
}
if ($fees_and_limits->fee_percent) {
if ($fees_and_limits->adjust_fee_percent) {
if($fees_and_limits->fee_percent == 100){ //unusual edge case if the user wishes to charge a fee of 100% 09/01/2022
$fee += $amount;
}
elseif ($fees_and_limits->adjust_fee_percent) {
$fee += round(($amount / (1 - $fees_and_limits->fee_percent / 100) - $amount), 2);
} else {
$fee += round(($amount * $fees_and_limits->fee_percent / 100), 2);
@ -383,42 +386,6 @@ class CompanyGateway extends BaseModel
return route('payment_webhook', ['company_key' => $this->company->company_key, 'company_gateway_id' => $this->hashed_id]);
}
/**
* we need to average out the gateway fees across all the invoices
* so lets iterate.
*
* we MAY need to adjust the final fee to ensure our rounding makes sense!
* @param $amount
* @param $invoice_count
* @return stdClass
*/
// public function calcGatewayFeeObject($amount, $invoice_count)
// {
// $total_gateway_fee = $this->calcGatewayFee($amount);
// $fee_object = new stdClass;
// $fees_and_limits = $this->getFeesAndLimits();
// if (! $fees_and_limits) {
// return $fee_object;
// }
// $fee_component_amount = $fees_and_limits->fee_amount ?: 0;
// $fee_component_percent = $fees_and_limits->fee_percent ? ($amount * $fees_and_limits->fee_percent / 100) : 0;
// $combined_fee_component = $fee_component_amount + $fee_component_percent;
// $fee_component_tax_name1 = $fees_and_limits->fee_tax_name1 ?: '';
// $fee_component_tax_rate1 = $fees_and_limits->fee_tax_rate1 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate1 / 100) : 0;
// $fee_component_tax_name2 = $fees_and_limits->fee_tax_name2 ?: '';
// $fee_component_tax_rate2 = $fees_and_limits->fee_tax_rate2 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate2 / 100) : 0;
// $fee_component_tax_name3 = $fees_and_limits->fee_tax_name3 ?: '';
// $fee_component_tax_rate3 = $fees_and_limits->fee_tax_rate3 ? ($combined_fee_component * $fees_and_limits->fee_tax_rate3 / 100) : 0;
// }
public function resolveRouteBinding($value, $field = null)
{
return $this

View File

@ -101,6 +101,7 @@ class Invoice extends BaseModel
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
'is_deleted' => 'bool',
];
protected $with = [];
@ -408,7 +409,19 @@ class Invoice extends BaseModel
$file_path = $this->client->invoice_filepath($invitation).$this->numberFormatter().'.pdf';
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
$file_exists = false;
/* Flysystem throws an exception if the path is "corrupted" so lets wrap it in a try catch and return a bool 06/01/2022*/
try{
$file_exists = Storage::disk(config('filesystems.default'))->exists($file_path);
}
catch(\Exception $e){
nlog($e->getMessage());
}
if(Ninja::isHosted() && $portal && $file_exists){
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
}
elseif(Ninja::isHosted()){
@ -416,7 +429,16 @@ class Invoice extends BaseModel
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
}
if(Storage::disk('public')->exists($file_path))
try{
$file_exists = Storage::disk('public')->exists($file_path);
}
catch(\Exception $e){
nlog($e->getMessage());
}
if($file_exists)
return Storage::disk('public')->{$type}($file_path);
$file_path = CreateEntityPdf::dispatchNow($invitation);

View File

@ -87,6 +87,7 @@ class Quote extends BaseModel
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
'is_deleted' => 'boolean',
];
protected $dates = [];
@ -117,6 +118,16 @@ class Quote extends BaseModel
return $this->dateMutator($value);
}
public function getStatusIdAttribute($value)
{
if($this->due_date && !$this->is_deleted && $value == Quote::STATUS_SENT && Carbon::parse($this->due_date)->lte(now()->startOfDay())){
return Quote::STATUS_EXPIRED;
}
return $value;
}
public function company()
{
return $this->belongsTo(Company::class);

View File

@ -0,0 +1,88 @@
<?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\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 EmailBounceNotification extends Notification
{
/**
* Create a new notification instance.
*
* @return void
*/
protected $account;
public function __construct($account)
{
$this->account = $account;
}
/**
* 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 = "Email bounce notification for Account {$this->account->key} \n";
$owner = $this->account->companies()->first()->owner();
$content .= "Owner {$owner->present()->name() } | {$owner->email}";
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content($content);
}
}

View File

@ -0,0 +1,88 @@
<?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\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 EmailSpamNotification extends Notification
{
/**
* Create a new notification instance.
*
* @return void
*/
protected $account;
public function __construct($account)
{
$this->account = $account;
}
/**
* 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 = "Email SPAM notification for Account {$this->account->key} \n";
$owner = $this->account->companies()->first()->owner();
$content .= "Owner {$owner->present()->name() } | {$owner->email}";
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content($content);
}
}

View File

@ -157,7 +157,8 @@ class AuthorizePaymentMethod
$paymentOne->setOpaqueData($op);
$contact = $this->authorize->client->primary_contact()->first();
$billto = false;
if ($contact) {
// Create the Bill To info for new payment type
$billto = new CustomerAddressType();

View File

@ -256,13 +256,13 @@ class BaseDriver extends AbstractPaymentDriver
$this->payment_hash->payment_id = $payment->id;
$this->payment_hash->save();
$this->attachInvoices($payment, $this->payment_hash);
if($this->payment_hash->credits_total() > 0)
$payment = $payment->service()->applyCredits($this->payment_hash)->save();
$payment->service()->updateInvoicePayment($this->payment_hash);
$this->attachInvoices($payment, $this->payment_hash);
event('eloquent.created: App\Models\Payment', $payment);
if ($this->client->getSetting('client_online_payment_notification') && in_array($status, [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING]))

View File

@ -49,7 +49,8 @@ class PayFastPaymentDriver extends BaseDriver
{
$types = [];
if($this->client->currency()->code == 'ZAR' || $this->client->currency()->code == 'USD')
// if($this->client->currency()->code == 'ZAR' || $this->client->currency()->code == 'USD')
if($this->client->currency()->code == 'ZAR')
$types[] = GatewayType::CREDIT_CARD;
return $types;

View File

@ -725,6 +725,17 @@ class StripePaymentDriver extends BaseDriver
return (new Verify($this))->run();
}
public function setApplePayDomain($domain)
{
$this->init();
\Stripe\ApplePayDomain::create([
'domain_name' => $domain,
],$this->stripe_connect_auth);
}
public function disconnect()
{
if(!$this->stripe_connect)

View File

@ -47,9 +47,13 @@ class PaymentRepository extends BaseRepository {
*/
public function save(array $data, Payment $payment): ?Payment
{
if ($payment->amount >= 0) {
// if ($payment->amount >= 0) {
// return $this->applyPayment($data, $payment);
// }
return $this->applyPayment($data, $payment);
}
return $payment;
}
@ -80,8 +84,8 @@ class PaymentRepository extends BaseRepository {
$client->service()->updatePaidToDate($data['amount'])->save();
}
elseif($data['amount'] >0){
// elseif($data['amount'] >0){
else{
//this fixes an edge case with unapplied payments
$client->service()->updatePaidToDate($data['amount'])->save();
}

View File

@ -38,6 +38,7 @@ class TriggeredActions extends AbstractService
{
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
$this->credit = $this->credit->service()->markSent()->save();
$this->sendEmail();
}

View File

@ -105,6 +105,7 @@ class AutoBillInvoice extends AbstractService
$fee = 0;
/* Build payment hash */
$payment_hash = PaymentHash::create([
'hash' => Str::random(64),
'data' => ['invoices' => [['invoice_id' => $this->invoice->hashed_id, 'amount' => $amount, 'invoice_number' => $this->invoice->number]]],
@ -123,7 +124,8 @@ class AutoBillInvoice extends AbstractService
->tokenBilling($gateway_token, $payment_hash);
}
catch(\Exception $e){
nlog($e->getMessage());
nlog("payment NOT captured for ". $this->invoice->number . " with error " . $e->getMessage());
// nlog($e->getMessage());
}
if($payment){

View File

@ -333,9 +333,10 @@ class InvoiceService
try{
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
if(Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf'))
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
if(Ninja::isHosted()) {
if(Ninja::isHosted() && Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf')) {
Storage::disk('public')->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
}

View File

@ -63,7 +63,8 @@ class MarkSent extends AbstractService
->service()
->applyNumber()
->setDueDate()
->deletePdf()
// ->deletePdf() //08-01-2022
->touchPdf() //08-01-2022
->setReminder()
->save();

View File

@ -49,6 +49,7 @@ class TriggeredActions extends AbstractService
}
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
$this->invoice->service()->markSent()->touchPdf()->save();
$this->sendEmail();
}

View File

@ -38,6 +38,7 @@ class TriggeredActions extends AbstractService
{
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
$this->quote = $this->quote->service()->markSent()->save();
$this->sendEmail();
}

View File

@ -42,4 +42,5 @@ trait Uploadable
}
}
}

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.3.41',
'app_tag' => '5.3.41',
'app_version' => '5.3.42',
'app_tag' => '5.3.42',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -24,7 +24,7 @@ class StripeConnectGateway extends Migration
'provider' => 'StripeConnect',
'sort_order' => 1,
'key' => 'd14dd26a47cecc30fdd65700bfb67b34',
'fields' => '{"account_id":""}'
'fields' => '{"account_id":"","appleDomainVerification":""}'
];
Gateway::create($gateway);

View File

@ -0,0 +1,44 @@
<?php
use App\Models\Gateway;
use App\Utils\Ninja;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddAppDomainIdToGatewaysTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if(Ninja::isHosted()){
$stripe_connect = Gateway::find(56);
if($stripe_connect){
$stripe_connect->fields = '{"account_id":"", "appleDomainVerification":""}';
$stripe_connect->save();
}
}
$stripe_connect = Gateway::find(20);
if($stripe_connect){
$stripe_connect->fields = '{"account_id":"", "appleDomainVerification":""}';
$stripe_connect->save();
}
}
/**
* Reverse the migrations.
*
* @return void
*/
}

View File

@ -44,7 +44,7 @@ class PaymentLibrariesSeeder extends Seeder
['id' => 17, 'name' => 'Pin', 'provider' => 'Pin', 'key' => '0749cb92a6b36c88bd9ff8aabd2efcab', 'fields' => '{"secretKey":"","testMode":false}'],
['id' => 18, 'name' => 'SagePay Direct', 'provider' => 'SagePay_Direct', 'key' => '4c8f4e5d0f353a122045eb9a60cc0f2d', 'fields' => '{"vendor":"","testMode":false,"referrerId":""}'],
['id' => 19, 'name' => 'SecurePay DirectPost', 'provider' => 'SecurePay_DirectPost', 'key' => '8036a5aadb2bdaafb23502da8790b6a2', 'fields' => '{"merchantId":"","transactionPassword":"","testMode":false,"enable_ach":"","enable_sofort":"","enable_apple_pay":"","enable_alipay":""}'],
['id' => 20, 'name' => 'Stripe', 'provider' => 'Stripe', 'sort_order' => 1, 'key' => 'd14dd26a37cecc30fdd65700bfb55b23', 'fields' => '{"publishableKey":"","apiKey":""}'],
['id' => 20, 'name' => 'Stripe', 'provider' => 'Stripe', 'sort_order' => 1, 'key' => 'd14dd26a37cecc30fdd65700bfb55b23', 'fields' => '{"publishableKey":"","apiKey":"","appleDomainVerification":""}'],
['id' => 21, 'name' => 'TargetPay Direct eBanking', 'provider' => 'TargetPay_Directebanking', 'key' => 'd14dd26a37cdcc30fdd65700bfb55b23', 'fields' => '{"subAccountId":""}'],
['id' => 22, 'name' => 'TargetPay Ideal', 'provider' => 'TargetPay_Ideal', 'key' => 'ea3b328bd72d381387281c3bd83bd97c', 'fields' => '{"subAccountId":""}'],
['id' => 23, 'name' => 'TargetPay Mr Cash', 'provider' => 'TargetPay_Mrcash', 'key' => 'a0035fc0d87c4950fb82c73e2fcb825a', 'fields' => '{"subAccountId":""}'],

View File

@ -4342,7 +4342,7 @@ $LANG = array(
'payment_type_instant_bank_pay' => 'Instant Bank Pay',
'payment_type_iDEAL' => 'iDEAL',
'payment_type_Przelewy24' => 'Przelewy24',
'payment_type_Mollie Bank Transfer' => 'Bank Transfer',
'payment_type_Mollie Bank Transfer' => 'Mollie Bank Transfer',
'payment_type_KBC/CBC' => 'KBC/CBC',
'payment_type_Instant Bank Pay' => 'Instant Bank Pay',
'payment_type_Hosted Page' => 'Hosted Page',

View File

@ -117,17 +117,22 @@
[data-ref="table"] > tbody > tr > td {
border-top: 1px solid #d8d8d8;
border-bottom: 1px solid #d8d8d8;
padding: 1.5rem;
padding: 1.5rem 1rem;
}
[data-ref="table"] > tbody > tr > td:first-child {
color: var(--primary-color);
}
[data-ref="table"] > thead > tr > th:last-child,
[data-ref="table"] > tbody > tr > td:last-child {
text-align: right;
}
[data-ref="table"] > thead > tr > th:last-child {
padding-right: 1rem;
}
[data-ref="table"] > tbody > tr:nth-child(odd) {
background-color: #f5f5f5;
}

View File

@ -208,6 +208,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::resource('subscriptions', 'SubscriptionController');
Route::post('subscriptions/bulk', 'SubscriptionController@bulk')->name('subscriptions.bulk');
Route::get('statics', 'StaticController');
Route::post('apple_pay/upload_file','ApplyPayController@upload');
});

View File

@ -44,3 +44,4 @@ Route::get('stripe/completed', 'StripeConnectController@completed')->name('strip
Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Checkout3dsController@index')->name('checkout.3ds_redirect');
Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Mollie3dsController@index')->name('mollie.3ds_redirect');
Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\GoCardlessController@ibpRedirect')->name('gocardless.ibp_redirect');
Route::get('.well-known/apple-developer-merchantid-domain-association', 'ClientPortal\ApplePayDomainController@showAppleMerchantId');

View File

@ -0,0 +1,92 @@
<?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 Tests\Feature;
use App\Models\CompanyGateway;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
* @covers App\Http\Controllers\ApplePayDomainController
*/
class ApplePayDomainMerchantUrlTest extends TestCase
{
use DatabaseTransactions;
use MockAccountData;
public function setUp() :void
{
parent::setUp();
$this->makeTestData();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testMerchantFieldGet()
{
if (! config('ninja.testvars.stripe')) {
$this->markTestSkipped('Skip test no company gateways installed');
}
$config = new \stdClass;
$config->publishableKey = "pk_test";
$config->apiKey = "sk_test";
$config->appleDomainVerification = "merchant_id";
$cg = new CompanyGateway;
$cg->company_id = $this->company->id;
$cg->user_id = $this->user->id;
$cg->gateway_key = 'd14dd26a37cecc30fdd65700bfb55b23';
$cg->require_cvv = true;
$cg->require_billing_address = true;
$cg->require_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(json_encode($config));
$cg->fees_and_limits = '';
$cg->save();
$response = $this->withHeaders([])->get('.well-known/apple-developer-merchantid-domain-association');
$arr = $response->getContent();
$response->assertStatus(200);
$this->assertEquals("merchant_id", $arr);
}
public function testDomainParsing()
{
$domain = 'http://ninja.test:8000';
$parsed = parse_url($domain);
$this->assertEquals('ninja.test', $parsed['host']);
$domain = 'ninja.test:8000';
$parsed = parse_url($domain);
$this->assertEquals('ninja.test', $parsed['host']);
$domain = 'http://ninja.test:8000/afadf/dfdfdf/dfdfasf';
$parsed = parse_url($domain);
$this->assertEquals('ninja.test', $parsed['host']);
}
}

View File

@ -0,0 +1,78 @@
<?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 Tests\Feature;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Session;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
* @covers App\Http\Controllers\InvoiceController
*/
class ClientDeletedInvoiceCreationTest extends TestCase
{
use MakesHash;
use DatabaseTransactions;
use MockAccountData;
public function setUp() :void
{
parent::setUp();
Session::start();
$this->faker = \Faker\Factory::create();
Model::reguard();
$this->makeTestData();
}
public function testClientedDeletedAttemptingToCreateInvoice()
{
/* Test fire new invoice */
$data = [
'client_id' => $this->client->hashed_id,
'number' => 'dude',
];
$this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/', $data)
->assertStatus(200);
$this->client->is_deleted = true;
$this->client->save();
$data = [
'client_id' => $this->client->hashed_id,
'number' => 'dude2',
];
$this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/', $data)
->assertStatus(302);
}
}

View File

@ -319,6 +319,71 @@ class CompanyGatewayApiTest extends TestCase
$this->assertEquals(10.2, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD));
}
public function testFeesAndLimitsFeePercentAndAmountCalcuationOneHundredPercent()
{
//{"1":{"min_limit":1,"max_limit":1000000,"fee_amount":10,"fee_percent":2,"fee_tax_name1":"","fee_tax_name2":"","fee_tax_name3":"","fee_tax_rate1":0,"fee_tax_rate2":0,"fee_tax_rate3":0,"fee_cap":10,"adjust_fee_percent":true}}
$fee = new FeesAndLimits;
$fee->fee_amount = 0;
$fee->fee_percent = 100;
// $fee->fee_tax_name1 = 'GST';
// $fee->fee_tax_rate1 = '10.0';
$fee_arr[1] = (array) $fee;
$data = [
'config' => 'random config',
'gateway_key' => '3b6621f970ab18887c4f6dca78d3f8bb',
'fees_and_limits' => $fee_arr,
];
/* POST */
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/company_gateways', $data);
$response->assertStatus(200);
$arr = $response->json();
$id = $this->decodePrimaryKey($arr['data']['id']);
$company_gateway = CompanyGateway::find($id);
$this->assertEquals(10, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD));
}
public function testFeesAndLimitsFeePercentAndAmountCalcuationOneHundredPercentVariationOne()
{
$fee = new FeesAndLimits;
$fee->fee_amount = 0;
$fee->fee_percent = 10;
$fee_arr[1] = (array) $fee;
$data = [
'config' => 'random config',
'gateway_key' => '3b6621f970ab18887c4f6dca78d3f8bb',
'fees_and_limits' => $fee_arr,
];
/* POST */
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/company_gateways', $data);
$response->assertStatus(200);
$arr = $response->json();
$id = $this->decodePrimaryKey($arr['data']['id']);
$company_gateway = CompanyGateway::find($id);
$this->assertEquals(1, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD));
}
public function testFeesAndLimitsFeePercentAndAmountAndTaxCalcuation()
{
//{"1":{"min_limit":1,"max_limit":1000000,"fee_amount":10,"fee_percent":2,"fee_tax_name1":"","fee_tax_name2":"","fee_tax_name3":"","fee_tax_rate1":0,"fee_tax_rate2":0,"fee_tax_rate3":0,"fee_cap":10,"adjust_fee_percent":true}}

View File

@ -220,4 +220,19 @@ class InvoiceTest extends TestCase
])->put('/api/v1/invoices/'.$arr['data']['id'], $data)
->assertStatus(200);
}
public function testClientedDeletedAttemptingToCreateInvoice()
{
/* Test fire new invoice */
$data = [
'client_id' => $this->client->hashed_id,
'number' => 'dude',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/', $data)
->assertStatus(200);
}
}