From 0186368f3920b9f260c25ee2ada61baf1c1c8e44 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Mar 2024 09:08:44 +1100 Subject: [PATCH 01/14] Do not force delete tokens when logging out --- app/Http/Controllers/LogoutController.php | 8 ++++++-- app/Livewire/RequiredClientInfo.php | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) 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/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 From 4c535a5be1bfbb9a2af409b66a66925f0df84bc4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Mar 2024 11:01:44 +1100 Subject: [PATCH 02/14] Fixes for template engine --- app/Filters/DesignFilters.php | 5 ++++- app/Services/Client/Statement.php | 1 + app/Services/Template/TemplateService.php | 2 -- 3 files changed, 5 insertions(+), 3 deletions(-) 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/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/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, From d3c286dacc822e9afe48f5d2200c4cc60b720518 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Mar 2024 11:25:20 +1100 Subject: [PATCH 03/14] Updated translations --- lang/en/texts.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lang/en/texts.php b/lang/en/texts.php index a1f5611e8150..7628afa05b1e 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5256,6 +5256,8 @@ $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', ); return $lang; From 1595c9d1f7fb0b24a5e704fca30d5eb364bf9f4b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Mar 2024 11:32:27 +1100 Subject: [PATCH 04/14] Stubs for rappen rounding --- app/Helpers/Invoice/InvoiceSum.php | 1 + app/Helpers/Invoice/InvoiceSumInclusive.php | 1 + 2 files changed, 2 insertions(+) diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index d6379356cd11..e1ae39609d80 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -250,6 +250,7 @@ class InvoiceSum } } /* Set new calculated total */ + /** @todo - rappen rounding here */ $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision); $this->invoice->total_taxes = $this->getTotalTaxes(); diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index 3786b55f99e6..13ebf8940924 100644 --- a/app/Helpers/Invoice/InvoiceSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceSumInclusive.php @@ -268,6 +268,7 @@ class InvoiceSumInclusive } /* Set new calculated total */ + /** @todo - rappen rounding here */ $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision); $this->invoice->total_taxes = $this->getTotalTaxes(); From 5f1e1ac174bec12a2f7be9c46d10fb0a9c85d430 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Mar 2024 12:52:08 +1100 Subject: [PATCH 05/14] Test routes for gateways --- app/PaymentDrivers/AuthorizePaymentDriver.php | 5 +++++ app/PaymentDrivers/BaseDriver.php | 5 +++++ app/PaymentDrivers/MolliePaymentDriver.php | 16 ++++++++++++++++ app/PaymentDrivers/StripePaymentDriver.php | 16 ++++++++++++++++ 4 files changed, 42 insertions(+) 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/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/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; + + } } From 094aadd6bec0e5195b0afb1f58229d2cf59fe76e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Mar 2024 14:21:42 +1100 Subject: [PATCH 06/14] Working on gateway authentication --- app/PaymentDrivers/BraintreePaymentDriver.php | 50 ++++++------------- .../CheckoutComPaymentDriver.php | 12 +++++ app/PaymentDrivers/EwayPaymentDriver.php | 9 ++++ app/PaymentDrivers/FortePaymentDriver.php | 32 ++++++++++-- 4 files changed, 64 insertions(+), 39 deletions(-) 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..16054c031e5d 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,29 @@ 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/"); + + // nlog("{$forte_base_uri}/organizations/org_{$forte_organization_id}/customers/"); + // {{baseURI}}/organizations/org_{{organizationID}}/locations/loc_{{locationID}}/customers + + return $response->successful(); + + } // public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) // { // return $this->payment_method->yourTokenBillingImplmentation(); From 397177fa0c96dfd4f6d0c84042380d203e0684ad Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Mar 2024 19:19:54 +1100 Subject: [PATCH 07/14] Fort Auth --- app/PaymentDrivers/FortePaymentDriver.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/PaymentDrivers/FortePaymentDriver.php b/app/PaymentDrivers/FortePaymentDriver.php index 16054c031e5d..3a246980a154 100644 --- a/app/PaymentDrivers/FortePaymentDriver.php +++ b/app/PaymentDrivers/FortePaymentDriver.php @@ -201,9 +201,6 @@ class FortePaymentDriver extends BaseDriver ->withHeaders(['X-Forte-Auth-Organization-Id' => $forte_organization_id]) ->get("{$forte_base_uri}/organizations/{$forte_organization_id}/locations/{$forte_location_id}/customers/"); - // nlog("{$forte_base_uri}/organizations/org_{$forte_organization_id}/customers/"); - // {{baseURI}}/organizations/org_{{organizationID}}/locations/loc_{{locationID}}/customers - return $response->successful(); } From 3de8df80662b1213416ec61cbc0c3b262fb6f114 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 16 Mar 2024 12:36:40 +1100 Subject: [PATCH 08/14] Create auth routes for gateways --- app/DataMapper/CompanySettings.php | 3 + app/Helpers/Invoice/InvoiceSum.php | 14 +++- app/Helpers/Invoice/InvoiceSumInclusive.php | 13 +++ app/Models/Client.php | 2 +- .../GoCardlessPaymentDriver.php | 13 +++ .../PayPalPPCPPaymentDriver.php | 13 +++ .../PayPalRestPaymentDriver.php | 71 +++------------- app/PaymentDrivers/PaytracePaymentDriver.php | 14 ++++ app/PaymentDrivers/SquarePaymentDriver.php | 13 +++ app/Services/Pdf/PdfMock.php | 14 +++- ..._adjust_discount_column_max_resolution.php | 1 - tests/Unit/InvoiceTest.php | 82 +++++++++++++++++++ 12 files changed, 187 insertions(+), 66 deletions(-) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index d2241f95b2f5..cbfaea8767cb 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -497,7 +497,10 @@ class CompanySettings extends BaseSettings public $use_unapplied_payment = 'off'; //always, option, off //@implemented + public $enable_rappen_rounding = false; + public static $casts = [ + 'enable_rappen_rounding' => 'bool', 'use_unapplied_payment' => 'string', 'show_pdfhtml_on_mobile' => 'bool', 'payment_email_all_contacts' => 'bool', diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index e1ae39609d80..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(); @@ -250,14 +254,22 @@ class InvoiceSum } } /* 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() { return $this->sub_total; diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index 13ebf8940924..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(); @@ -271,10 +275,19 @@ class InvoiceSumInclusive /** @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/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/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/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/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/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/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() From 8a54df3cf6c330e9218aeaf0d1dce9933666fe23 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 17 Mar 2024 12:07:20 +1100 Subject: [PATCH 09/14] Update blacklist --- app/Http/Controllers/CompanyGatewayController.php | 9 +++++++++ app/Http/ValidationRules/Account/BlackListRule.php | 1 + routes/api.php | 2 ++ 3 files changed, 12 insertions(+) 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/ValidationRules/Account/BlackListRule.php b/app/Http/ValidationRules/Account/BlackListRule.php index 0d74f646b31a..cb09e5dae521 100644 --- a/app/Http/ValidationRules/Account/BlackListRule.php +++ b/app/Http/ValidationRules/Account/BlackListRule.php @@ -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/routes/api.php b/routes/api.php index 311360ae66d8..1f770642aa3d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -194,7 +194,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']); From 80b944f822826a82c43e6224e08846810b5fd175 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 17 Mar 2024 15:25:31 +1100 Subject: [PATCH 10/14] Add SMS checks --- app/DataProviders/SMSNumbers.php | 88381 ++++++++++++++++ .../TestCompanyGatewayRequest.php | 49 + .../ValidationRules/Account/BlackListRule.php | 2 +- app/Jobs/Mail/NinjaMailerJob.php | 2 +- app/Libraries/MultiDB.php | 5 + app/Services/Email/Email.php | 2 +- tests/Unit/SmsNumberTest.php | 36 + 7 files changed, 88474 insertions(+), 3 deletions(-) create mode 100644 app/DataProviders/SMSNumbers.php create mode 100644 app/Http/Requests/CompanyGateway/TestCompanyGatewayRequest.php create mode 100644 tests/Unit/SmsNumberTest.php diff --git a/app/DataProviders/SMSNumbers.php b/app/DataProviders/SMSNumbers.php new file mode 100644 index 000000000000..2335316e43f5 --- /dev/null +++ b/app/DataProviders/SMSNumbers.php @@ -0,0 +1,88381 @@ +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 cb09e5dae521..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 */ diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index f421667a31a4..f39fe20001f0 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -224,7 +224,7 @@ class NinjaMailerJob implements ShouldQueue private function incrementEmailCounter(): void { - if(in_array($this->mailer, ['default','mailgun'])) + 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..778ae5f064d8 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::numberExists($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/Services/Email/Email.php b/app/Services/Email/Email.php index f47189009b3f..6631f0d2ae3d 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -246,7 +246,7 @@ class Email implements ShouldQueue private function incrementEmailCounter(): void { - if(in_array($this->mailer, ['default','mailgun'])) + if(in_array($this->mailer, ['default','mailgun','postmark'])) Cache::increment("email_quota".$this->company->account->key); } 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 From 2757fae1f2db10cedc368a31379c37d9b55f93c9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 17 Mar 2024 16:04:44 +1100 Subject: [PATCH 11/14] Adjustments for email quotas --- app/Export/CSV/BaseExport.php | 1 + app/Models/Account.php | 6 +++--- lang/en/texts.php | 1 + lang/fr_CA/texts.php | 10 ++++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 37e904d154c6..da0ca9a0d147 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -294,6 +294,7 @@ class BaseExport 'line_total' => '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/Models/Account.php b/app/Models/Account.php index 382eec8692ee..2a6b60717b63 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 @@ -504,10 +504,10 @@ class Account extends BaseModel if ($this->isPaid()) { $limit = $this->paid_plan_email_quota; - $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 50; + $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 30; } 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/lang/en/texts.php b/lang/en/texts.php index 7628afa05b1e..bb56e8675177 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5258,6 +5258,7 @@ $lang = array( '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; From 8383dc1c6d88efcdec91c187eb5a30c916c8e1bd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 17 Mar 2024 16:06:06 +1100 Subject: [PATCH 12/14] Adjustments for email quotas --- app/Models/Account.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Models/Account.php b/app/Models/Account.php index 2a6b60717b63..4fc236da4971 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -503,8 +503,10 @@ 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() * 30; + $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * (20 * $multiplier); } else { $limit = $this->free_plan_email_quota; $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 1.5; From d5a8fe724970b562458b131c4ebb89e7034543e8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 17 Mar 2024 16:07:12 +1100 Subject: [PATCH 13/14] Fixes for tests --- app/Libraries/MultiDB.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 778ae5f064d8..aaa48916ee47 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -538,7 +538,7 @@ class MultiDB $current_db = config('database.default'); - if(SMSNumbers::numberExists($phone)){ + if(SMSNumbers::hasNumber($phone)){ return true; } From 633ca319f52c1371cc3b96112cbfef4eeaf17f6a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 17 Mar 2024 18:05:49 +1100 Subject: [PATCH 14/14] SMS checks --- app/DataProviders/SMSNumbers.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/DataProviders/SMSNumbers.php b/app/DataProviders/SMSNumbers.php index 2335316e43f5..0b777f477714 100644 --- a/app/DataProviders/SMSNumbers.php +++ b/app/DataProviders/SMSNumbers.php @@ -88367,6 +88367,23 @@ class SMSNumbers '+998977468084', '+998977826068', '+998978245689', + "+33757056033", + "+33757055849", + "+33757055952", + "+33757055909", + "+33757055846", + "+33757056037", + "+33757056029", + "+33757055917", + "+33757056047", + "+33757055828", + "+33757055926", + "+33757055902", + "+33757056015", + "+33757055990", + "+33757055967", + "+33757055913", + "+33757055835", ]; public static function getNumbers(): array