diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 5e7e6a9f8de6..17249805bb5a 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -499,25 +499,28 @@ class CompanySettings extends BaseSettings public $use_unapplied_payment = 'off'; //always, option, off //@implemented + public $enable_rappen_rounding = false; + public static $casts = [ - 'use_unapplied_payment' => 'string', - 'show_pdfhtml_on_mobile' => 'bool', - 'payment_email_all_contacts' => 'bool', - 'statement_design_id' => 'string', - 'delivery_note_design_id' => 'string', - 'payment_receipt_design_id' => 'string', - 'payment_refund_design_id' => 'string', - 'classification' => 'string', - 'enable_e_invoice' => 'bool', - 'classification' => 'string', - 'default_expense_payment_type_id' => 'string', - 'e_invoice_type' => 'string', - 'mailgun_endpoint' => 'string', - 'client_initiated_payments' => 'bool', - 'client_initiated_payments_minimum' => 'float', - 'sync_invoice_quote_columns' => 'bool', - 'show_task_item_description' => 'bool', - 'allow_billable_task_items' => 'bool', + 'enable_rappen_rounding' => 'bool', + 'use_unapplied_payment' => 'string', + 'show_pdfhtml_on_mobile' => 'bool', + 'payment_email_all_contacts' => 'bool', + 'statement_design_id' => 'string', + 'delivery_note_design_id' => 'string', + 'payment_receipt_design_id' => 'string', + 'payment_refund_design_id' => 'string', + 'classification' => 'string', + 'enable_e_invoice' => 'bool', + 'classification' => 'string', + 'default_expense_payment_type_id' => 'string', + 'e_invoice_type' => 'string', + 'mailgun_endpoint' => 'string', + 'client_initiated_payments' => 'bool', + 'client_initiated_payments_minimum' => 'float', + 'sync_invoice_quote_columns' => 'bool', + 'show_task_item_description' => 'bool', + 'allow_billable_task_items' => 'bool', 'accept_client_input_quote_approval' => 'bool', 'custom_sending_email' => 'string', 'show_paid_stamp' => 'bool', diff --git a/app/DataProviders/SMSNumbers.php b/app/DataProviders/SMSNumbers.php new file mode 100644 index 000000000000..0b777f477714 --- /dev/null +++ b/app/DataProviders/SMSNumbers.php @@ -0,0 +1,88398 @@ + 'item.line_total', 'gross_line_total' => 'item.gross_line_total', 'tax_amount' => 'item.tax_amount', + 'product_cost' => 'item.product_cost' ]; protected array $quote_report_keys = [ diff --git a/app/Filters/DesignFilters.php b/app/Filters/DesignFilters.php index 69c6cf60df55..45071226a161 100644 --- a/app/Filters/DesignFilters.php +++ b/app/Filters/DesignFilters.php @@ -58,7 +58,10 @@ class DesignFilters extends QueryFilters public function entities(string $entities = ''): Builder { - + + if(stripos($entities, 'statement') !== false) + $entities = 'client'; + if (strlen($entities) == 0 || str_contains($entities, ',')) { return $this->builder; } diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index d6379356cd11..f142ce0b9f4a 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -52,6 +52,7 @@ class InvoiceSum public InvoiceItemSum $invoice_items; + private $rappen_rounding = false; /** * Constructs the object with Invoice and Settings object. * @@ -63,8 +64,11 @@ class InvoiceSum if ($this->invoice->client) { $this->precision = $this->invoice->client->currency()->precision; + $this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding'); } else { $this->precision = $this->invoice->vendor->currency()->precision; + $this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding'); + } $this->tax_map = new Collection(); @@ -252,11 +256,20 @@ class InvoiceSum /* Set new calculated total */ $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision); + if($this->rappen_rounding) + $this->invoice->amount = $this->roundRappen($this->invoice->amount); + $this->invoice->total_taxes = $this->getTotalTaxes(); return $this; } + + function roundRappen($value): float + { + return round($value / .05, 0) * .05; + } + public function getSubTotal() { return $this->sub_total; diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index 3786b55f99e6..d7c8a75dbeae 100644 --- a/app/Helpers/Invoice/InvoiceSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceSumInclusive.php @@ -47,6 +47,8 @@ class InvoiceSumInclusive private $precision; + private $rappen_rounding = false; + public InvoiceItemSumInclusive $invoice_items; /** * Constructs the object with Invoice and Settings object. @@ -59,8 +61,10 @@ class InvoiceSumInclusive if ($this->invoice->client) { $this->precision = $this->invoice->client->currency()->precision; + $this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding'); } else { $this->precision = $this->invoice->vendor->currency()->precision; + $this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding'); } $this->tax_map = new Collection(); @@ -268,12 +272,22 @@ class InvoiceSumInclusive } /* Set new calculated total */ + /** @todo - rappen rounding here */ $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision); + if($this->rappen_rounding) { + $this->invoice->amount = $this->roundRappen($this->invoice->amount); + } + $this->invoice->total_taxes = $this->getTotalTaxes(); return $this; } + + function roundRappen($value): float + { + return round($value / .05, 0) * .05; + } public function getSubTotal() { diff --git a/app/Http/Controllers/CompanyGatewayController.php b/app/Http/Controllers/CompanyGatewayController.php index 6c58f090a248..732787b2d317 100644 --- a/app/Http/Controllers/CompanyGatewayController.php +++ b/app/Http/Controllers/CompanyGatewayController.php @@ -20,6 +20,7 @@ use App\Http\Requests\CompanyGateway\DestroyCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\EditCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\ShowCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\StoreCompanyGatewayRequest; +use App\Http\Requests\CompanyGateway\TestCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest; use App\Jobs\Util\ApplePayDomain; use App\Models\Client; @@ -535,4 +536,12 @@ class CompanyGatewayController extends BaseController return $this->listResponse(CompanyGateway::withTrashed()->company()->whereIn('id', $request->ids)); } + + public function test(TestCompanyGatewayRequest $request, CompanyGateway $company_gateway) + { + + return response()->json(['message' => $company_gateway->driver()->auth() ? 'true' : 'false'], 200); + + } + } diff --git a/app/Http/Controllers/LogoutController.php b/app/Http/Controllers/LogoutController.php index 2889b777d0dd..423a382e71e1 100644 --- a/app/Http/Controllers/LogoutController.php +++ b/app/Http/Controllers/LogoutController.php @@ -63,8 +63,12 @@ class LogoutController extends BaseController $ct->company ->tokens() ->where('is_system', true) - ->forceDelete(); - + ->cursor() + ->each(function ($ct){ + $ct->token = \Illuminate\Support\Str::random(64); + $ct->save(); + }); + return response()->json(['message' => 'All tokens deleted'], 200); } } diff --git a/app/Http/Requests/CompanyGateway/TestCompanyGatewayRequest.php b/app/Http/Requests/CompanyGateway/TestCompanyGatewayRequest.php new file mode 100644 index 000000000000..8442dc91cd72 --- /dev/null +++ b/app/Http/Requests/CompanyGateway/TestCompanyGatewayRequest.php @@ -0,0 +1,49 @@ +user(); + + return $user->isAdmin(); + } + + public function rules() + { + + return [ + + ]; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $this->replace($input); + } +} diff --git a/app/Http/ValidationRules/Account/BlackListRule.php b/app/Http/ValidationRules/Account/BlackListRule.php index 0d74f646b31a..e8149d23bc7b 100644 --- a/app/Http/ValidationRules/Account/BlackListRule.php +++ b/app/Http/ValidationRules/Account/BlackListRule.php @@ -5,7 +5,7 @@ * @link https://github.com/invoiceninja/invoiceninja source repository * * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com) - * + *1` * @license https://www.elastic.co/licensing/elastic-license */ @@ -21,6 +21,7 @@ class BlackListRule implements ValidationRule { /** Bad domains +/- dispoable email domains */ private array $blacklist = [ + 'wireconnected.com', 'secure-coinspot.com', 'casasotombo.com', 'otpku.com', diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index ae5e0a5f2e74..bff1f3f5aa7c 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -229,8 +229,8 @@ class NinjaMailerJob implements ShouldQueue private function incrementEmailCounter(): void { - if (in_array($this->mailer, ['default', 'mailgun'])) - Cache::increment("email_quota" . $this->company->account->key); + if(in_array($this->mailer, ['default','mailgun','postmark'])) + Cache::increment("email_quota".$this->company->account->key); } /** diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index fd028a03811c..aaa48916ee47 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -21,6 +21,7 @@ use Illuminate\Support\Str; use App\Models\CompanyToken; use App\Models\ClientContact; use App\Models\VendorContact; +use App\DataProviders\SMSNumbers; use Illuminate\Support\Facades\DB; /** @@ -537,6 +538,10 @@ class MultiDB $current_db = config('database.default'); + if(SMSNumbers::hasNumber($phone)){ + return true; + } + foreach (self::$dbs as $db) { self::setDB($db); if ($exists = Account::where('account_sms_verification_number', $phone)->where('account_sms_verified', true)->exists()) { diff --git a/app/Livewire/RequiredClientInfo.php b/app/Livewire/RequiredClientInfo.php index 733f9a0b4559..c661bbcb8c53 100644 --- a/app/Livewire/RequiredClientInfo.php +++ b/app/Livewire/RequiredClientInfo.php @@ -222,8 +222,9 @@ class RequiredClientInfo extends Component $this->show_form = true; $hash = Cache::get(request()->input('hash')); + $invoice = Invoice::find($this->decodePrimaryKey($hash['invoice_id'])); - $this->invoice_terms = Invoice::find($this->decodePrimaryKey($hash['invoice_id']))->terms; + $this->invoice_terms = $invoice->terms; } count($this->fields) > 0 || $this->show_terms diff --git a/app/Models/Account.php b/app/Models/Account.php index 382eec8692ee..4fc236da4971 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -102,7 +102,7 @@ class Account extends BaseModel private $free_plan_email_quota = 20; - private $paid_plan_email_quota = 400; + private $paid_plan_email_quota = 300; /** * @var string @@ -503,11 +503,13 @@ class Account extends BaseModel } if ($this->isPaid()) { + $multiplier = $this->plan == 'enterprise' ? 2 : 1.2; + $limit = $this->paid_plan_email_quota; - $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 50; + $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * (20 * $multiplier); } else { $limit = $this->free_plan_email_quota; - $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 2; + $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 1.5; } return min($limit, 1000); diff --git a/app/Models/Client.php b/app/Models/Client.php index 61bda7230946..6ac021a4bc43 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -485,7 +485,7 @@ class Client extends BaseModel implements HasLocalePreference } /*Company Settings*/ - elseif ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) { + elseif ((property_exists($this->company->settings, $setting) !== false) && (isset($this->company->settings->{$setting}) !== false)) { return $this->company->settings->{$setting}; } elseif (property_exists(CompanySettings::defaults(), $setting)) { return CompanySettings::defaults()->{$setting}; diff --git a/app/PaymentDrivers/AuthorizePaymentDriver.php b/app/PaymentDrivers/AuthorizePaymentDriver.php index a9ac9d568367..861df0c50993 100644 --- a/app/PaymentDrivers/AuthorizePaymentDriver.php +++ b/app/PaymentDrivers/AuthorizePaymentDriver.php @@ -195,4 +195,9 @@ class AuthorizePaymentDriver extends BaseDriver { return (new AuthorizeCustomer($this))->importCustomers(); } + + public function auth(): bool + { + return $this->init()->getPublicClientKey() ?? false; + } } diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index 8ea61173467d..5739a8d08321 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -806,4 +806,9 @@ class BaseDriver extends AbstractPaymentDriver { return true; } + + public function auth(): bool + { + return true; + } } diff --git a/app/PaymentDrivers/BraintreePaymentDriver.php b/app/PaymentDrivers/BraintreePaymentDriver.php index 6f90f0cd1482..ec64d62f9a5f 100644 --- a/app/PaymentDrivers/BraintreePaymentDriver.php +++ b/app/PaymentDrivers/BraintreePaymentDriver.php @@ -157,34 +157,6 @@ class BraintreePaymentDriver extends BaseDriver } } - // public function updateCustomer() - // { - // $customer = $this->findOrCreateCustomer(); - - // $result = $this->gateway->customer()->update( - // $customer->id, - // [ - // 'firstName' => $this->client->present()->name(), - // 'email' => $this->client->present()->email(), - // 'phone' => $this->client->present()->phone(), - // 'creditCard' => [ - // 'billingAddress' => [ - // 'options' => [ - // 'updateExisting' => true - // ], - // 'firstName' => $this->client->present()->first_name() ?: $this->client->present()->name(), - // 'lastName' => $this->client->present()->last_name() ?: '', - // 'streetAddress' => $this->client->address1 ?: '', - // 'extendedAddress' =>$this->client->address2 ?: '', - // 'locality' => $this->client->city ?: '', - // 'postalCode' => $this->client->postal_code ?: '', - // 'countryCodeAlpha2' => $this->client->country ? $this->client->country->iso_3166_2 : 'US', - // ], - // ], - // ] - // ); - // } - public function refund(Payment $payment, $amount, $return_client_response = false) { $this->init(); @@ -324,13 +296,21 @@ class BraintreePaymentDriver extends BaseDriver nlog('braintree webhook'); - // if($webhookNotification) - // nlog($webhookNotification->kind); - - // // Example values for webhook notification properties - // $message = $webhookNotification->kind; // "subscription_went_past_due" - // $message = $webhookNotification->timestamp->format('D M j G:i:s T Y'); // "Sun Jan 1 00:00:00 UTC 2012" - return response()->json([], 200); } + + public function auth(): bool + { + + try { + $ct =$this->init()->gateway->clientToken()->generate(); + nlog($ct); + return true; + } + catch(\Exception $e) { + + } + + return false; + } } diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 176a763507b9..65b51718b8c4 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -534,4 +534,16 @@ class CheckoutComPaymentDriver extends BaseDriver { // Gateway doesn't support this feature. } + + public function auth(): bool + { + try{ + $this->init()->gateway->getCustomersClient('x'); + return true; + } + catch(\Exception $e){ + + } + return false; + } } diff --git a/app/PaymentDrivers/EwayPaymentDriver.php b/app/PaymentDrivers/EwayPaymentDriver.php index 92459d460d32..2ce3deb8b986 100644 --- a/app/PaymentDrivers/EwayPaymentDriver.php +++ b/app/PaymentDrivers/EwayPaymentDriver.php @@ -211,4 +211,13 @@ class EwayPaymentDriver extends BaseDriver return $fields; } + + public function auth(): bool + { + + $response =$this->init()->eway->queryTransaction('xx'); + + return (bool) count($response->getErrors()) == 0; + + } } diff --git a/app/PaymentDrivers/FortePaymentDriver.php b/app/PaymentDrivers/FortePaymentDriver.php index 2a9ebb440ad3..3a246980a154 100644 --- a/app/PaymentDrivers/FortePaymentDriver.php +++ b/app/PaymentDrivers/FortePaymentDriver.php @@ -11,13 +11,14 @@ namespace App\PaymentDrivers; -use App\Jobs\Util\SystemLogger; -use App\Models\GatewayType; use App\Models\Payment; use App\Models\SystemLog; -use App\PaymentDrivers\Forte\ACH; -use App\PaymentDrivers\Forte\CreditCard; +use App\Models\GatewayType; +use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; +use App\PaymentDrivers\Forte\ACH; +use Illuminate\Support\Facades\Http; +use App\PaymentDrivers\Forte\CreditCard; class FortePaymentDriver extends BaseDriver { @@ -183,6 +184,26 @@ class FortePaymentDriver extends BaseDriver ]; } + public function auth(): bool + { + + $forte_base_uri = "https://sandbox.forte.net/api/v3/"; + if ($this->company_gateway->getConfigField('testMode') == false) { + $forte_base_uri = "https://api.forte.net/v3/"; + } + $forte_api_access_id = $this->company_gateway->getConfigField('apiAccessId'); + $forte_secure_key = $this->company_gateway->getConfigField('secureKey'); + $forte_auth_organization_id = $this->company_gateway->getConfigField('authOrganizationId'); + $forte_organization_id = $this->company_gateway->getConfigField('organizationId'); + $forte_location_id = $this->company_gateway->getConfigField('locationId'); + + $response = Http::withBasicAuth($forte_api_access_id, $forte_secure_key) + ->withHeaders(['X-Forte-Auth-Organization-Id' => $forte_organization_id]) + ->get("{$forte_base_uri}/organizations/{$forte_organization_id}/locations/{$forte_location_id}/customers/"); + + return $response->successful(); + + } // public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) // { // return $this->payment_method->yourTokenBillingImplmentation(); diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index 0ec94d1032c5..afd08c49a6aa 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -558,4 +558,17 @@ class GoCardlessPaymentDriver extends BaseDriver { return render('gateways.gocardless.verification'); } + + public function auth(): bool + { + try { + $customers = $this->init()->gateway->customers()->list(); + return true; + } + catch(\Exception $e){ + + } + + return false; + } } diff --git a/app/PaymentDrivers/MolliePaymentDriver.php b/app/PaymentDrivers/MolliePaymentDriver.php index 57adb0ca2698..ed02ba31a835 100644 --- a/app/PaymentDrivers/MolliePaymentDriver.php +++ b/app/PaymentDrivers/MolliePaymentDriver.php @@ -420,4 +420,20 @@ class MolliePaymentDriver extends BaseDriver { return \number_format((float) $amount, 2, '.', ''); } + + public function auth(): bool + { + $this->init(); + + try { + $p = $this->gateway->payments->page(); + return true; + } + catch(\Exception $e){ + + } + + return false; + + } } diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php index 5b757d907600..25fb85094884 100644 --- a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php +++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php @@ -560,5 +560,18 @@ class PayPalPPCPPaymentDriver extends BaseDriver PayPalWebhook::dispatch($request->all(), $request->headers->all(), $this->access_token); } + + public function auth(): bool + { + try { + $this->init()->getClientToken(); + return true; + } + catch(\Exception $e) { + + } + + return false; + } } diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php index f08decd67501..ae6d68e6f301 100644 --- a/app/PaymentDrivers/PayPalRestPaymentDriver.php +++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php @@ -81,11 +81,7 @@ class PayPalRestPaymentDriver extends BaseDriver public function init() { - // $this->omnipay_gateway = Omnipay::create( - // $this->company_gateway->gateway->provider - // ); - // $this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig()); $this->api_endpoint_url = $this->company_gateway->getConfigField('testMode') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; $secret = $this->company_gateway->getConfigField('secret'); @@ -380,61 +376,6 @@ class PayPalRestPaymentDriver extends BaseDriver return $r->json()['id']; - - - // $_invoice = collect($this->payment_hash->data->invoices)->first(); - - // $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id)); - - // $order = [ - // "intent" => "CAPTURE", - // "payer" => [ - // "name" => [ - // "given_name" => $this->client->present()->first_name(), - // "surname" => $this->client->present()->last_name(), - // ], - // "email_address" => $this->client->present()->email(), - // "address" => [ - // "address_line_1" => $this->client->address1, - // "address_line_2" => $this->client->address2, - // "admin_area_1" => $this->client->city, - // "admin_area_2" => $this->client->state, - // "postal_code" => $this->client->postal_code, - // "country_code" => $this->client->country->iso_3166_2, - // ] - // ], - // "purchase_units" => [ - // [ - // "description" => ctrans('texts.invoice_number').'# '.$invoice->number, - // "invoice_id" => $invoice->number, - // "amount" => [ - // "value" => (string)$data['amount_with_fee'], - // "currency_code" => $this->client->currency()->code, - // "breakdown" => [ - // "item_total" => [ - // "currency_code" => $this->client->currency()->code, - // "value" => (string)$data['amount_with_fee'] - // ] - // ] - // ], - // "items" => [ - // [ - // "name" => ctrans('texts.invoice_number').'# '.$invoice->number, - // "quantity" => "1", - // "unit_amount" => [ - // "currency_code" => $this->client->currency()->code, - // "value" => (string)$data['amount_with_fee'] - // ], - // ], - // ], - // ] - // ] - // ]; - - // $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); - - // return $r->json()['id']; - } private function getShippingAddress(): ?array @@ -520,5 +461,17 @@ class PayPalRestPaymentDriver extends BaseDriver return 0; } + public function auth(): bool + { + try { + $this->init()->getClientToken(); + return true; + } + catch(\Exception $e) { + + } + + return false; + } } diff --git a/app/PaymentDrivers/PaytracePaymentDriver.php b/app/PaymentDrivers/PaytracePaymentDriver.php index 4f76b95645a2..2d262e105cae 100644 --- a/app/PaymentDrivers/PaytracePaymentDriver.php +++ b/app/PaymentDrivers/PaytracePaymentDriver.php @@ -246,4 +246,18 @@ class PaytracePaymentDriver extends BaseDriver return false; } + + public function auth(): bool + { + try { + $this->init()->generateAuthHeaders() && strlen($this->company_gateway->getConfigField('integratorId')) > 2; + return true; + } + catch(\Exception $e){ + + } + + return false; + + } } diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index d58aa4f7cf9a..f0420f1af8e3 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -429,4 +429,17 @@ class SquarePaymentDriver extends BaseDriver return $amount; } + + public function auth(): bool + { + + $api_response = $this->init() + ->square + ->getCustomersApi() + ->listCustomers(); + + + return (bool) count($api_response->getErrors()) == 0; + + } } diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index b128b6baa68b..f440debb0232 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -990,4 +990,20 @@ class StripePaymentDriver extends BaseDriver return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UCS-2BE'); }, $string); } + + public function auth(): bool + { + $this->init(); + + try { + $this->verifyConnect(); + return true; + } + catch(\Exception $e) { + + } + + return false; + + } } diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index bedcacb34af4..163718ea6dbf 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -162,6 +162,7 @@ class Statement $ts->addGlobal(['show_credits' => $this->options['show_credits_table']]); $ts->addGlobal(['show_aging' => $this->options['show_aging_table']]); $ts->addGlobal(['show_payments' => $this->options['show_payments_table']]); + $ts->addGlobal(['currency_code' => $this->client->company->currency()->code]); $ts->build([ 'variables' => collect([$variables]), diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index 6600067687ce..c07d34515a0f 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -250,8 +250,8 @@ class Email implements ShouldQueue private function incrementEmailCounter(): void { - if (in_array($this->mailer, ['default', 'mailgun'])) - Cache::increment("email_quota" . $this->company->account->key); + if(in_array($this->mailer, ['default','mailgun','postmark'])) + Cache::increment("email_quota".$this->company->account->key); } /** diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index d6fb4e5aa53c..1afc3ef8a49b 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -113,24 +113,30 @@ class PdfMock /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Invoice::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = InvoiceInvitation::factory()->make(); break; case 'quote': /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Quote::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = QuoteInvitation::factory()->make(); break; case 'credit': /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Credit::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = CreditInvitation::factory()->make(); break; case 'purchase_order': - /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ + + /** @var \App\Models\PurchaseOrder $entity */ $entity = PurchaseOrder::factory()->make(); - $entity->client = Client::factory()->make(['settings' => $settings]); + // $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->vendor = Vendor::factory()->make(); + $entity->vendor->setRelation('company', $this->company); $entity->invitation = PurchaseOrderInvitation::factory()->make(); break; case PurchaseOrder::class: @@ -138,17 +144,17 @@ class PdfMock $entity = PurchaseOrder::factory()->make(); $entity->invitation = PurchaseOrderInvitation::factory()->make(); $entity->vendor = Vendor::factory()->make(); + $entity->invitation->setRelation('company', $this->company); break; default: $entity = false; break; } - $entity->tax_map = $this->getTaxMap(); $entity->total_tax_map = $this->getTotalTaxMap(); $entity->invitation->company = $this->company; - + return $entity; } diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index e9fcced20a5e..d3710ba722b0 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -613,8 +613,6 @@ class TemplateService $this->payment = $payment; - $this->addGlobal(['currency_code' => $payment->currency->code ?? $this->company->currency()->code]); - $credits = $payment->credits->map(function ($credit) use ($payment) { return [ 'credit' => $credit->number, diff --git a/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php b/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php index 3aa0099e3cf0..ad36915c62d2 100644 --- a/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php +++ b/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php @@ -16,7 +16,6 @@ return new class extends Migration $table->decimal('discount', 20, 6)->default(0)->change(); }); - Schema::table('credits', function (Blueprint $table) { $table->decimal('discount', 20, 6)->default(0)->change(); }); diff --git a/lang/en/texts.php b/lang/en/texts.php index 628953919c3b..b3d101fc4542 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5259,6 +5259,9 @@ $lang = array( 'select_email_provider' => 'Set your email as the sending user', 'purchase_order_items' => 'Purchase Order Items', 'csv_rows_length' => 'No data found in this CSV file', + 'accept_payments_online' => 'Accept Payments Online', + 'all_payment_gateways' => 'View all payment gateways', + 'product_cost' => 'Product cost', ); return $lang; diff --git a/lang/fr_CA/texts.php b/lang/fr_CA/texts.php index 9faef9cfe54c..b69b5fca52d9 100644 --- a/lang/fr_CA/texts.php +++ b/lang/fr_CA/texts.php @@ -460,7 +460,7 @@ $lang = array( 'edit_token' => 'Éditer le jeton', 'delete_token' => 'Supprimer le jeton', 'token' => 'Jeton', - 'add_gateway' => 'Add Payment Gateway', + 'add_gateway' => 'Ajouter une passerelle de paiement', 'delete_gateway' => 'Supprimer la passerelle', 'edit_gateway' => 'Éditer la passerelle', 'updated_gateway' => 'La passerelle a été mise à jour', @@ -5248,9 +5248,11 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'payment_type_help' => 'Définit le type de paiement manuel par défaut.', 'quote_valid_until_help' => 'Le nombre de jours pour lesquels la soumission est valide', 'expense_payment_type_help' => 'Le type de paiement de dépenses par défaut à utiliser', - 'paylater' => 'Pay in 4', - 'payment_provider' => 'Payment Provider', - + 'paylater' => 'Payer en 4', + 'payment_provider' => 'Fournisseur de paiement', + 'select_email_provider' => 'Définir le courriel pour l\'envoi', + 'purchase_order_items' => 'Articles du bon d\'achat', + 'csv_rows_length' => 'Aucune donnée dans ce fichier CSV', ); return $lang; diff --git a/routes/api.php b/routes/api.php index 5cba623f988b..bf2be5dcbb0b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -195,7 +195,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::get('company_ledger', [CompanyLedgerController::class, 'index'])->name('company_ledger.index'); Route::resource('company_gateways', CompanyGatewayController::class); + Route::post('company_gateways/bulk', [CompanyGatewayController::class, 'bulk'])->name('company_gateways.bulk'); + Route::post('company_gateways/{company_gateway}/test', [CompanyGatewayController::class, 'test'])->name('company_gateways.test'); Route::put('company_users/{user}', [CompanyUserController::class, 'update']); Route::put('company_users/{user}/preferences', [CompanyUserController::class, 'updatePreferences']); diff --git a/tests/Unit/InvoiceTest.php b/tests/Unit/InvoiceTest.php index b87106079bb5..d9b9cab429e6 100644 --- a/tests/Unit/InvoiceTest.php +++ b/tests/Unit/InvoiceTest.php @@ -49,6 +49,88 @@ class InvoiceTest extends TestCase $this->invoice_calc = new InvoiceSum($this->invoice); } + public function testRappenRounding() + { + + $c_settings = $this->client->settings; + $c_settings->enable_rappen_rounding = true; + + $c = \App\Models\Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'settings' => $c_settings, + ]); + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10.01; + $item->type_id = '1'; + $item->tax_id = '1'; + $line_items[] = $item; + + $i = Invoice::factory()->create([ + 'discount' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $c->id, + 'line_items' => $line_items, + 'status_id' => 1, + ]); + + $invoice_calc = new InvoiceSum($i); + $ii = $invoice_calc->build()->getInvoice(); + + $this->assertEquals(10, $ii->amount); + + } + + public function testRappenRoundingUp() + { + + $c_settings = $this->client->settings; + $c_settings->enable_rappen_rounding = true; + + $c = \App\Models\Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'settings' => $c_settings, + ]); + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10.09; + $item->type_id = '1'; + $item->tax_id = '1'; + $line_items[] = $item; + + $i = Invoice::factory()->create([ + 'discount' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $c->id, + 'line_items' => $line_items, + 'status_id' => 1, + ]); + + $invoice_calc = new InvoiceSum($i); + $ii = $invoice_calc->build()->getInvoice(); + + $this->assertEquals(10.10, round($ii->amount,2)); + + } + public function testPartialDueDateCast() { $i = Invoice::factory() diff --git a/tests/Unit/SmsNumberTest.php b/tests/Unit/SmsNumberTest.php new file mode 100644 index 000000000000..496501e38764 --- /dev/null +++ b/tests/Unit/SmsNumberTest.php @@ -0,0 +1,36 @@ +assertTrue(SMSNumbers::hasNumber("+461614222")); + } + + public function testArrayMiss() + { + $this->assertFalse(SMSNumbers::hasNumber("+5485454")); + } + + public function testSmsArrayType() + { + $this->assertIsArray(SMSNumbers::getNumbers()); + } +} \ No newline at end of file