diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index 7dde1e3bc9..df64204634 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -25,6 +25,7 @@ use App\Models\Account; use App\Models\Company; use App\Models\Country; use App\Models\Expense; +use App\Models\Gateway; use App\Models\Invoice; use App\Models\Product; use App\Models\Project; diff --git a/app/DataMapper/EmailTemplateDefaults.php b/app/DataMapper/EmailTemplateDefaults.php index b63dba59c7..93be2e4e98 100644 --- a/app/DataMapper/EmailTemplateDefaults.php +++ b/app/DataMapper/EmailTemplateDefaults.php @@ -92,6 +92,11 @@ class EmailTemplateDefaults case 'email_subject_custom3': return self::emailInvoiceSubject(); + case 'email_vendor_notification_subject': + return self::emailVendorNotificationSubject(); + + case 'email_vendor_notification_body': + return self::emailVendorNotificationBody(); default: return self::emailInvoiceTemplate(); @@ -99,6 +104,16 @@ class EmailTemplateDefaults } } + public static function emailVendorNotificationSubject() + { + return self::transformText('vendor_notification_subject'); + } + + public static function emailVendorNotificationBody() + { + return self::transformText('vendor_notification_body'); + } + public static function emailInvoiceSubject() { return ctrans('texts.invoice_subject', ['number'=>'$number', 'account'=>'$company.name']); diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index 29178f5236..6579ed3f04 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -11,16 +11,17 @@ namespace App\Export\CSV; -use App\Libraries\MultiDB; -use App\Models\Client; -use App\Models\Company; -use App\Transformers\ClientContactTransformer; -use App\Transformers\ClientTransformer; use App\Utils\Ninja; use App\Utils\Number; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\App; +use App\Models\Client; use League\Csv\Writer; +use App\Models\Company; +use App\Libraries\MultiDB; +use Illuminate\Support\Facades\App; +use App\Export\Decorators\Decorator; +use App\Transformers\ClientTransformer; +use Illuminate\Database\Eloquent\Builder; +use App\Transformers\ClientContactTransformer; class ClientExport extends BaseExport { @@ -32,6 +33,8 @@ class ClientExport extends BaseExport public string $date_key = 'created_at'; + private Decorator $decorator; + public array $entity_keys = [ 'address1' => 'client.address1', 'address2' => 'client.address2', @@ -84,6 +87,8 @@ class ClientExport extends BaseExport $this->input = $input; $this->client_transformer = new ClientTransformer(); $this->contact_transformer = new ClientContactTransformer(); + $this->decorator = new Decorator(); + } public function returnJson() diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index c92fc5983f..86ee229bfd 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -11,14 +11,15 @@ namespace App\Export\CSV; -use App\Libraries\MultiDB; +use App\Utils\Ninja; +use League\Csv\Writer; use App\Models\Company; use App\Models\Payment; -use App\Transformers\PaymentTransformer; -use App\Utils\Ninja; -use Illuminate\Database\Eloquent\Builder; +use App\Libraries\MultiDB; use Illuminate\Support\Facades\App; -use League\Csv\Writer; +use App\Transformers\PaymentTransformer; +use Illuminate\Database\Eloquent\Builder; +use App\Export\Decorators\Decorator; class PaymentExport extends BaseExport { @@ -28,13 +29,16 @@ class PaymentExport extends BaseExport public Writer $csv; + private Decorator $decorator; + public function __construct(Company $company, array $input) { $this->company = $company; $this->input = $input; $this->entity_transformer = new PaymentTransformer(); + $this->decorator = new Decorator(); } - + private function init(): Builder { @@ -113,6 +117,8 @@ class PaymentExport extends BaseExport } elseif (array_key_exists($key, $transformed_entity)) { $entity[$key] = $transformed_entity[$key]; } else { + + // $entity[$key] = $this->decorator->transform($key, $payment); $entity[$key] = $this->resolveKey($key, $payment, $this->entity_transformer); } diff --git a/app/Export/Decorators/ClientDecorator.php b/app/Export/Decorators/ClientDecorator.php index 83fbdad948..3af0cef276 100644 --- a/app/Export/Decorators/ClientDecorator.php +++ b/app/Export/Decorators/ClientDecorator.php @@ -11,10 +11,165 @@ namespace App\Export\Decorators; -class ClientDecorator implements DecoratorInterface +use App\Models\Client; + +class ClientDecorator extends Decorator implements DecoratorInterface { - public function transform(): string + private $entity_key = 'client'; + + public function transform(string $key, mixed $entity): mixed { - return 'Payment Decorator'; + $client = false; + + if($entity instanceof Client){ + $client = $entity; + } + elseif($entity->client) { + $client = $entity->client; + } + + if($client && method_exists($this, $key)) { + return $this->{$key}($client); + } + + return ''; } + + public function name(Client $client) { + return $client->present()->name(); + } + public function number(Client $client) { + return $client->number ?? ''; + } + public function user(Client $client) { + return $client->user->present()->name(); + } + public function assigned_user(Client $client) { + return $client->assigned_user ? $client->user->present()->name() : ''; + } + public function balance(Client $client) { + return $client->balance ?? 0; + } + public function paid_to_date(Client $client) { + return $client->paid_to_date ?? 0; + } + public function currency_id(Client $client) { + return $client->currency() ? $client->currency()->code : $client->company->currency()->code; + } + public function website(Client $client) { + return $client->website ?? ''; + } + public function private_notes(Client $client) { + return $client->private_notes ?? ''; + } + public function industry_id(Client $client) { + return $client->industry ? ctrans("texts.industry_{$client->industry->name}") : ''; + } + public function size_id(Client $client) { + return $client->size ? ctrans("texts.size_{$client->size->name}") : ''; + } + public function phone(Client $client) { + return $client->phone ?? ''; + } + public function address1(Client $client) { + return $client->address1 ?? ''; + } + public function address2(Client $client) { + return $client->address2 ?? ''; + } + public function city(Client $client) { + return $client->city ?? ''; + } + public function state(Client $client) { + return $client->state ?? ''; + } + public function postal_code(Client $client) { + return $client->postal_code ?? ''; + } + public function country_id(Client $client) { + return $client->country ? ctrans("texts.country_{$client->country->name}") : ''; + } + public function shipping_address1(Client $client) { + return $client->shipping_address1 ?? ''; + } + public function shipping_address2(Client $client) { + return $client->shipping_address2 ?? ''; + } + public function shipping_city(Client $client) { + return $client->shipping_city ?? ''; + } + public function shipping_state(Client $client) { + return $client->shipping_state ?? ''; + } + public function shipping_postal_code(Client $client) { + return $client->shipping_postal_code ?? ''; + } + public function shipping_country_id(Client $client) { + return $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : ''; + } + public function payment_terms(Client $client) { + return $client?->settings?->payment_terms ?? $client->company->settings->payment_terms; + } + public function vat_number(Client $client) { + return $client->vat_number ?? ''; + } + public function id_number(Client $client) { + return $client->id_number ?? ''; + } + public function public_notes(Client $client) { + return $client->public_notes ?? ''; + } + public function custom_value1(Client $client) { + return $client->custom_value1 ?? ''; + } + public function custom_value2(Client $client) { + return $client->custom_value2 ?? ''; + } + public function custom_value3(Client $client) { + return $client->custom_value3 ?? ''; + } + public function custom_value4(Client $client) { + return $client->custom_value4 ?? ''; + } + public function payment_balance(Client $client) { + return $client->payment_balance ?? 0; + } + public function credit_balance(Client $client) { + return $client->credit_balance ?? 0; + } + public function classification(Client $client) { + ctrans("texts.{$client->classification}") ?? ''; + } + + + + ////////contact details///////////////// + /* + public function phone(Client $client) { + + } + public function first_name(Client $client) { + + } + public function last_name(Client $client) { + + } + public function email(Client $client) { + + } + public function custom_value1(Client $client) { + + } + public function custom_value2(Client $client) { + + } + public function custom_value3(Client $client) { + + } + public function custom_value4(Client $client) { + + } + */ + + } diff --git a/app/Export/Decorators/ContactDecorator.php b/app/Export/Decorators/ContactDecorator.php new file mode 100644 index 0000000000..b6588ce44f --- /dev/null +++ b/app/Export/Decorators/ContactDecorator.php @@ -0,0 +1,20 @@ + $value = (new ClientDecorator($entity, $key))->transform(), - ($entity instanceof Payment) => $value = (new PaymentDecorator($entity, $key))->transform(), - ($entity instanceof Invoice) => $value = (new InvoiceDecorator($entity, $key))->transform(), - ($entity instanceof RecurringInvoice) => $value = (new RecurringInvoiceDecorator($entity, $key))->transform(), - ($entity instanceof Credit) => $value = (new CreditDecorator($entity, $key))->transform(), - ($entity instanceof Quote) => $value = (new QuoteDecorator($entity, $key))->transform(), - ($entity instanceof Task) => $value = (new TaskDecorator($entity, $key))->transform(), - ($entity instanceof Expense) => $value = (new ExpenseDecorator($entity, $key))->transform(), - ($entity instanceof Project) => $value = (new ProjectDecorator($entity, $key))->transform(), - ($entity instanceof Product) => $value = (new ProductDecorator($entity, $key))->transform(), - ($entity instanceof Vendor) => $value = (new VendorDecorator($entity, $key))->transform(), - ($entity instanceof PurchaseOrder) => $value = (new PurchaseOrderDecorator($entity, $key))->transform(), - default => $value = '', - }; - - return $value; } + public function transform(string $key, mixed $entity): mixed + { + $index = $this->getKeyPart(0, $key); + $column = $this->getKeyPart(1, $key); + + return $this->{$index}()->transform($column, $entity); + + } + + public function invoice(): InvoiceDecorator + { + return new InvoiceDecorator(); + } + + public function client(): ClientDecorator + { + return new ClientDecorator(); + } + + public function contact(): ContactDecorator + { + return new ContactDecorator(); + } + + public function vendor_contact(): VendorContactDecorator + { + return new VendorContactDecorator(); + } + + public function payment(): PaymentDecorator + { + return new PaymentDecorator(); + } + + public function credit(): CreditDecorator + { + return new CreditDecorator(); + } + + public function vendor(): VendorDecorator + { + return new VendorDecorator(); + } + + public function expense(): ExpenseDecorator + { + return new ExpenseDecorator(); + } + + public function product(): ProductDecorator + { + return new ProductDecorator(); + } + + public function project(): ProjectDecorator + { + return new ProjectDecorator(); + } + + public function task(): TaskDecorator + { + return new TaskDecorator(); + } + + public function quote(): QuoteDecorator + { + return new QuoteDecorator(); + } + + public function recurring_invoice(): RecurringInvoiceDecorator + { + return new RecurringInvoiceDecorator(); + } + + public function purchase_order(): PurchaseOrderDecorator + { + return new PurchaseOrderDecorator(); + } + + public function getKeyPart(int $index, string $key): string + { + $parts = explode('.', $key); + + return $parts[$index]; + } } diff --git a/app/Export/Decorators/DecoratorInterface.php b/app/Export/Decorators/DecoratorInterface.php index a19b231f8f..cd8b6fa035 100644 --- a/app/Export/Decorators/DecoratorInterface.php +++ b/app/Export/Decorators/DecoratorInterface.php @@ -12,5 +12,5 @@ namespace App\Export\Decorators; interface DecoratorInterface { - public function transform(): string; + public function transform(string $key, mixed $entity): mixed; } diff --git a/app/Export/Decorators/ExpenseDecorator.php b/app/Export/Decorators/ExpenseDecorator.php index 828d28690e..718a442855 100644 --- a/app/Export/Decorators/ExpenseDecorator.php +++ b/app/Export/Decorators/ExpenseDecorator.php @@ -13,7 +13,7 @@ namespace App\Export\Decorators; class ExpenseDecorator implements DecoratorInterface { - public function transform(): string + public function transform(string $key, mixed $entity): mixed { return 'Payment Decorator'; } diff --git a/app/Export/Decorators/InvoiceDecorator.php b/app/Export/Decorators/InvoiceDecorator.php index 2898965c7a..f3eb80c4c8 100644 --- a/app/Export/Decorators/InvoiceDecorator.php +++ b/app/Export/Decorators/InvoiceDecorator.php @@ -11,9 +11,9 @@ namespace App\Export\Decorators; -class InvoiceDecorator implements DecoratorInterface +class InvoiceDecorator extends Decorator implements DecoratorInterface { - public function transform(): string + public function transform(string $key, mixed $entity): mixed { return 'Payment Decorator'; } diff --git a/app/Export/Decorators/PaymentDecorator.php b/app/Export/Decorators/PaymentDecorator.php index 9a287d4a08..f9af06b182 100644 --- a/app/Export/Decorators/PaymentDecorator.php +++ b/app/Export/Decorators/PaymentDecorator.php @@ -11,10 +11,132 @@ namespace App\Export\Decorators; -class PaymentDecorator implements DecoratorInterface{ +use App\Models\Payment; - public function transform(): string +class PaymentDecorator extends Decorator implements DecoratorInterface{ + + private $entity_key = 'payment'; + + public function transform(string $key, $entity): mixed { - return 'Payment Decorator'; + $payment = false; + + if($entity instanceof Payment){ + $payment = $entity; + } + elseif($entity->payment) { + $payment = $entity->payment; + } + + if($key == 'amount' && (!$entity instanceof Payment)){ + return $entity->payments()->exists() ? $entity->payments()->withoutTrashed()->sum('paymentables.amount') : ctrans('texts.unpaid'); + } + elseif($key == 'refunded' && (!$entity instanceof Payment)) { + return $entity->payments()->exists() ? $entity->payments()->withoutTrashed()->sum('paymentables.refunded') : ''; + } + elseif($key == 'applied' && (!$entity instanceof Payment)) { + $refunded = $entity->payments()->withoutTrashed()->sum('paymentables.refunded'); + $amount = $entity->payments()->withoutTrashed()->sum('paymentables.amount'); + return $entity->payments()->withoutTrashed()->exists() ? ($amount - $refunded) : ''; + } + + if($payment && method_exists($this, $key)) { + return $this->{$key}($payment); + } + + return ''; } + + public function date(Payment $payment) { + return $payment->date ?? ''; + } + + public function amount(Payment $payment) { + return $payment->amount ?? ''; + } + + public function refunded(Payment $payment) { + return $payment->refunded ?? ''; + } + + public function applied(Payment $payment) { + return $payment->applied ?? ''; + } + public function transaction_reference(Payment $payment) { + return $payment->transaction_reference ?? ''; + } + public function currency(Payment $payment) { + return $payment->currency()->exists() ? $payment->currency->code : $payment->company->currency()->code; + } + + public function exchange_rate(Payment $payment) { + return $payment->exchange_rate ?? 1; + } + + public function number(Payment $payment) { + return $payment->number ?? ''; + } + + public function method(Payment $payment) { + return $payment->translatedType(); + } + + public function status(Payment $payment) { + return $payment->stringStatus($payment->status_id); + } + + public function private_notes(Payment $payment) { + return strip_tags($payment->private_notes) ?? ''; + } + + public function custom_value1(Payment $payment) { + return $payment->custom_value1 ?? ''; + } + + public function custom_value2(Payment $payment) { + return $payment->custom_value2 ?? ''; + } + + public function custom_value3(Payment $payment) { + return $payment->custom_value3 ?? ''; + } + + public function custom_value4(Payment $payment) { + return $payment->custom_value4 ?? ''; + } + + public function user_id(Payment $payment) { + return $payment->user ? $payment->user->present()->name() : ''; + } + + public function assigned_user_id(Payment $payment) { + return $payment->assigned_user ? $payment->assigned_user->present()->name() : ''; + } + + public function project_id(Payment $payment) { + return $payment->project()->exists() ? $payment->project->name : ''; + } + + /////////////////////////////////////////////////// + + public function vendor_id(Payment $payment){ + return $payment->vendor()->exists() ? $payment->vendor->name : ''; + } + + public function exchange_currency(Payment $payment){ + return $payment->exchange_currency()->exists() ? $payment->exchange_currency->code : ''; + } + + public function gateway_type_id(Payment $payment) { + return $payment->gateway_type ? $payment->gateway_type->name : 'Unknown Type'; + } + + public function client_id(Payment $payment) { + return $payment->client->present()->name(); + } + + public function type_id(Payment $payment) { + return $payment->translatedType(); + } + } \ No newline at end of file diff --git a/app/Export/Decorators/ProductDecorator.php b/app/Export/Decorators/ProductDecorator.php index b22d940fbe..d7b9fc7295 100644 --- a/app/Export/Decorators/ProductDecorator.php +++ b/app/Export/Decorators/ProductDecorator.php @@ -13,7 +13,7 @@ namespace App\Export\Decorators; class ProductDecorator implements DecoratorInterface { - public function transform(): string + public function transform(string $key, mixed $entity): mixed { return 'Payment Decorator'; } diff --git a/app/Export/Decorators/ProjectDecorator.php b/app/Export/Decorators/ProjectDecorator.php index 62b5d8ee91..91ce5b87ff 100644 --- a/app/Export/Decorators/ProjectDecorator.php +++ b/app/Export/Decorators/ProjectDecorator.php @@ -13,7 +13,7 @@ namespace App\Export\Decorators; class ProjectDecorator implements DecoratorInterface { - public function transform(): string + public function transform(string $key, mixed $entity): mixed { return 'Payment Decorator'; } diff --git a/app/Export/Decorators/PurchaseOrderDecorator.php b/app/Export/Decorators/PurchaseOrderDecorator.php index c5d8dd3a34..b25f7001d5 100644 --- a/app/Export/Decorators/PurchaseOrderDecorator.php +++ b/app/Export/Decorators/PurchaseOrderDecorator.php @@ -11,9 +11,9 @@ namespace App\Export\Decorators; -class PurchaseOrderDecorator implements DecoratorInterface +class PurchaseOrderDecorator extends Decorator implements DecoratorInterface { - public function transform(): string + public function transform(string $key, mixed $entity): mixed { return 'Payment Decorator'; } diff --git a/app/Export/Decorators/QuoteDecorator.php b/app/Export/Decorators/QuoteDecorator.php index 96161c96d4..49e073452c 100644 --- a/app/Export/Decorators/QuoteDecorator.php +++ b/app/Export/Decorators/QuoteDecorator.php @@ -11,9 +11,9 @@ namespace App\Export\Decorators; -class QuoteDecorator implements DecoratorInterface +class QuoteDecorator extends Decorator implements DecoratorInterface { - public function transform(): string + public function transform(string $key, mixed $entity): mixed { return 'Payment Decorator'; } diff --git a/app/Export/Decorators/RecurringInvoiceDecorator.php b/app/Export/Decorators/RecurringInvoiceDecorator.php index d14e6f705e..02cd646b5d 100644 --- a/app/Export/Decorators/RecurringInvoiceDecorator.php +++ b/app/Export/Decorators/RecurringInvoiceDecorator.php @@ -11,9 +11,9 @@ namespace App\Export\Decorators; -class RecurringInvoiceDecorator implements DecoratorInterface +class RecurringInvoiceDecorator extends Decorator implements DecoratorInterface { - public function transform(): string + public function transform(string $key, mixed $entity): mixed { return 'Payment Decorator'; } diff --git a/app/Export/Decorators/TaskDecorator.php b/app/Export/Decorators/TaskDecorator.php index 60005ad59a..a6f8254c66 100644 --- a/app/Export/Decorators/TaskDecorator.php +++ b/app/Export/Decorators/TaskDecorator.php @@ -11,9 +11,9 @@ namespace App\Export\Decorators; -class TaskDecorator implements DecoratorInterface +class TaskDecorator extends Decorator implements DecoratorInterface { - public function transform(): string + public function transform(string $key, mixed $entity): mixed { return 'Payment Decorator'; } diff --git a/app/Export/Decorators/VendorContactDecorator.php b/app/Export/Decorators/VendorContactDecorator.php new file mode 100644 index 0000000000..0e1b1d26ad --- /dev/null +++ b/app/Export/Decorators/VendorContactDecorator.php @@ -0,0 +1,20 @@ +format('Y-m-d H:i:s'); + $client = auth()->guard('contact')->user()->client; + $client->private_notes = $trial_started; $client->fill($request->all()); $client->save(); @@ -150,6 +153,7 @@ class NinjaPlanController extends Controller $account->plan_expires = now()->addDays(14); $account->is_trial=true; $account->hosted_company_count = 10; + $account->trial_started = now(); $account->save(); } diff --git a/app/Http/Controllers/CompanyGatewayController.php b/app/Http/Controllers/CompanyGatewayController.php index 9914327f52..990a886f90 100644 --- a/app/Http/Controllers/CompanyGatewayController.php +++ b/app/Http/Controllers/CompanyGatewayController.php @@ -194,21 +194,27 @@ class CompanyGatewayController extends BaseController */ public function store(StoreCompanyGatewayRequest $request) { - $company_gateway = CompanyGatewayFactory::create(auth()->user()->company()->id, auth()->user()->id); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $company_gateway = CompanyGatewayFactory::create($user->company()->id, $user->id); $company_gateway->fill($request->all()); $company_gateway->save(); - /*Always ensure at least one fees and limits object is set per gateway*/ - if (! isset($company_gateway->fees_and_limits)) { - $gateway_types = $company_gateway->driver(new Client)->gatewayTypes(); - - $fees_and_limits = new \stdClass; - $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits; - - $company_gateway->fees_and_limits = $fees_and_limits; - $company_gateway->save(); + /*Always ensure at least one fees and limits object is set per gateway*/ + $gateway_types = $company_gateway->driver(new Client)->getAvailableMethods(); + + $fees_and_limits = $company_gateway->fees_and_limits; + + foreach($gateway_types as $key => $gateway_type) + { + if(!property_exists($fees_and_limits, $key)) + $fees_and_limits->{$key} = new FeesAndLimits; } + $company_gateway->fees_and_limits = $fees_and_limits; + $company_gateway->save(); + ApplePayDomain::dispatch($company_gateway, $company_gateway->company->db); if (in_array($company_gateway->gateway_key, $this->stripe_keys)) { @@ -381,10 +387,18 @@ class CompanyGatewayController extends BaseController { $company_gateway->fill($request->all()); - if (! $request->has('fees_and_limits')) { - $company_gateway->fees_and_limits = ''; + /*Always ensure at least one fees and limits object is set per gateway*/ + $gateway_types = $company_gateway->driver(new Client)->getAvailableMethods(); + + $fees_and_limits = $company_gateway->fees_and_limits; + + foreach($gateway_types as $key => $gateway_type) { + if(!property_exists($fees_and_limits, $key)) { + $fees_and_limits->{$key} = new FeesAndLimits; + } } + $company_gateway->fees_and_limits = $fees_and_limits; $company_gateway->save(); if($company_gateway->gateway_key == $this->checkout_key) { diff --git a/app/Http/Requests/CompanyGateway/StoreCompanyGatewayRequest.php b/app/Http/Requests/CompanyGateway/StoreCompanyGatewayRequest.php index 5f148f5043..d007de2427 100644 --- a/app/Http/Requests/CompanyGateway/StoreCompanyGatewayRequest.php +++ b/app/Http/Requests/CompanyGateway/StoreCompanyGatewayRequest.php @@ -11,6 +11,7 @@ namespace App\Http\Requests\CompanyGateway; +use App\DataMapper\FeesAndLimits; use App\Http\Requests\Request; use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule; use App\Models\Gateway; @@ -28,7 +29,10 @@ class StoreCompanyGatewayRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin(); } public function rules() @@ -64,6 +68,7 @@ class StoreCompanyGatewayRequest extends Request if (isset($input['fees_and_limits'])) { $input['fees_and_limits'] = $this->cleanFeesAndLimits($input['fees_and_limits']); } + } $this->replace($input); diff --git a/app/Http/Requests/CompanyGateway/UpdateCompanyGatewayRequest.php b/app/Http/Requests/CompanyGateway/UpdateCompanyGatewayRequest.php index 465449cce2..3949bdb804 100644 --- a/app/Http/Requests/CompanyGateway/UpdateCompanyGatewayRequest.php +++ b/app/Http/Requests/CompanyGateway/UpdateCompanyGatewayRequest.php @@ -27,7 +27,10 @@ class UpdateCompanyGatewayRequest extends Request */ public function authorize() { - return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin(); } public function rules() diff --git a/app/Jobs/Account/CreateAccount.php b/app/Jobs/Account/CreateAccount.php index 4dc50f4a2a..7c487ca8fe 100644 --- a/app/Jobs/Account/CreateAccount.php +++ b/app/Jobs/Account/CreateAccount.php @@ -92,6 +92,8 @@ class CreateAccount $spaa9f78 = (new CreateUser($this->request, $sp794f3f, $sp035a66, true))->handle(); + $sp035a66->service()->localizeCompany($spaa9f78); + (new CreateCompanyPaymentTerms($sp035a66, $spaa9f78))->handle(); (new CreateCompanyTaskStatuses($sp035a66, $spaa9f78))->handle(); @@ -124,8 +126,6 @@ class CreateAccount NinjaMailerJob::dispatch($nmo, true); - // \Modules\Admin\Jobs\Account\NinjaUser::dispatch([], $sp035a66); - (new \Modules\Admin\Jobs\Account\NinjaUser([], $sp035a66))->handle(); } diff --git a/app/Jobs/Company/CreateCompany.php b/app/Jobs/Company/CreateCompany.php index a9377fa478..984c29993c 100644 --- a/app/Jobs/Company/CreateCompany.php +++ b/app/Jobs/Company/CreateCompany.php @@ -84,6 +84,7 @@ class CreateCompany match($settings->country_id) { '724' => $company = $this->spanishSetup($company), '36' => $company = $this->australiaSetup($company), + '710' => $company = $this->southAfticaSetup($company), default => $company->save(), }; @@ -103,7 +104,7 @@ class CreateCompany if(request()->hasHeader('cf-ipcountry')) { - $c = Country::where('iso_3166_2', request()->header('cf-ipcountry'))->first(); + $c = Country::query()->where('iso_3166_2', request()->header('cf-ipcountry'))->first(); if($c) { return (string)$c->id; @@ -115,7 +116,7 @@ class CreateCompany if($details && property_exists($details, 'countryCode')) { - $c = Country::where('iso_3166_2', $details->countryCode)->first(); + $c = Country::query()->where('iso_3166_2', $details->countryCode)->first(); if($c) { return (string)$c->id; @@ -153,15 +154,6 @@ class CreateCompany $company->save(); - //user does not exist yet. - // MultiDB::setDb($company->db); - // $user = \App\Models\User::where('account_id', $company->account_id)->first(); - - // $tax_rate = TaxRateFactory::create($company->id, $user->id); - // $tax_rate->name = $company->tax_data->regions->EU->subregions->ES->tax_name; - // $tax_rate->rate = $company->tax_data->regions->EU->subregions->ES->tax_rate; - // $tax_rate->save(); - return $company; } catch(\Exception $e) { @@ -174,6 +166,40 @@ class CreateCompany } + private function southAfticaSetup(Company $company): Company + { + + try { + + $company->enabled_item_tax_rates = 1; + $company->enabled_tax_rates = 1; + + $translations = new \stdClass; + $translations->invoice = "Tax Invoice"; + + $settings = $company->settings; + $settings->currency_id = '4'; + $settings->timezone_id = '56'; + $settings->translations = $translations; + + $company->settings = $settings; + + $company->save(); + + return $company; + + } catch(\Exception $e) { + nlog($e->getMessage()); + nlog("SETUP: could not complete setup for South African Locale"); + } + + $company->save(); + + return $company; + + + } + private function australiaSetup(Company $company): Company { try { @@ -193,17 +219,6 @@ class CreateCompany $company->save(); - //$user = $company->account->users()->first(); - //user does not exist yet. - // MultiDB::setDb($company->db); - // $user = \App\Models\User::where('account_id', $company->account_id)->first(); - - - // $tax_rate = TaxRateFactory::create($company->id, $user->id); - // $tax_rate->name = $company->tax_data->regions->AU->subregions->AU->tax_name; - // $tax_rate->rate = $company->tax_data->regions->AU->subregions->AU->tax_rate; - // $tax_rate->save(); - return $company; } catch(\Exception $e) { diff --git a/app/Jobs/Expense/VendorExpenseNotify.php b/app/Jobs/Expense/VendorExpenseNotify.php new file mode 100644 index 0000000000..16a6b83a33 --- /dev/null +++ b/app/Jobs/Expense/VendorExpenseNotify.php @@ -0,0 +1,99 @@ +db); + + $this->expense->vendor->contacts->filter(function(VendorContact $contact){ + return $contact->send_email && $contact->email; + })->each(function(VendorContact $contact){ + $this->notify($contact); + }); + } + + private function notify(VendorContact $contact) + { + + $mo = new EmailObject; + $mo->contact = $contact; + $mo->vendor_contact_id = $contact->id; + $mo->user_id = $this->expense->user_id; + $mo->company_key = $this->expense->company->company_key; + $mo->subject = ctrans('texts.vendor_notification_subject', [ + 'amount' => Number::formatMoney($this->expense->amount, $contact->vendor), + 'vendor' => $contact->vendor->present()->name(), + ]); + $mo->body = ctrans('texts.vendor_notification_body', [ + 'vendor' => $this->expense->vendor->present()->name(), + 'amount' => Number::formatMoney($this->expense->amount, $contact->vendor), + 'contact' => $contact->present()->name(), + 'payment_date' => $this->translateDate($this->expense->payment_date, $this->expense->company->date_format(), $this->expense->vendor->locale()), + 'transaction_reference' => $this->expense->transaction_reference ?? '', + 'number' => $this->expense->number, + ]); + $mo->template = ''; + $mo->email_template_body = 'vendor_notification_subject'; + $mo->email_template_subject = 'vendor_notification_body'; + $mo->vendor_id = $contact->vendor_id ?? null; + $mo->variables = [ + 'amount' => Number::formatMoney($this->expense->amount, $contact->vendor), + 'contact' => $contact->present()->name(), + 'vendor' => $contact->vendor->present()->name(), + 'payment_date' => $this->translateDate($this->expense->payment_date, $this->expense->company->date_format(), $this->expense->vendor->locale()), + 'transaction_reference' => $this->expense->transaction_reference ?? '', + 'number' => $this->expense->number, + ]; + + Email::dispatch($mo, $this->expense->company); + + $fields = new \stdClass(); + $fields->expense_id = $this->expense->id; + $fields->vendor_id = $contact->vendor_id; + $fields->vendor_contact_id = $contact->id; + $fields->user_id = $this->expense->user_id; + $fields->company_id = $contact->company_id; + $fields->activity_type_id = Activity::VENDOR_NOTIFICATION_EMAIL; + $fields->account_id = $this->expense->company->account_id; + + $activity_repo = new ActivityRepository(); + $activity_repo->save($fields, $this->expense, Ninja::eventVars()); + + } +} \ No newline at end of file diff --git a/app/Listeners/LogRequestSending.php b/app/Listeners/LogRequestSending.php new file mode 100644 index 0000000000..c123ef8092 --- /dev/null +++ b/app/Listeners/LogRequestSending.php @@ -0,0 +1,43 @@ +request->headers()); + nlog($event->request->url()); + nlog(json_encode($event->request->headers())); + nlog($event->request->body()); + + } +} diff --git a/app/Listeners/LogResponseReceived.php b/app/Listeners/LogResponseReceived.php new file mode 100644 index 0000000000..77dff20299 --- /dev/null +++ b/app/Listeners/LogResponseReceived.php @@ -0,0 +1,56 @@ +request->headers()); + nlog($event->request->url()); + nlog(json_encode($event->request->headers())); + nlog($event->request->body()); + + nlog("Response"); + nlog($event->response->headers()); + nlog(json_encode($event->response->headers())); + nlog($event->response->body()); + nlog($event->response->json()); + } +} diff --git a/app/Models/Activity.php b/app/Models/Activity.php index e8b1a7f05d..311ee6b614 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -257,6 +257,8 @@ class Activity extends StaticModel const PAYMENT_EMAILED = 138; + const VENDOR_NOTIFICATION_EMAIL = 139; + protected $casts = [ 'is_system' => 'boolean', 'updated_at' => 'timestamp', diff --git a/app/Models/Company.php b/app/Models/Company.php index d177a8108f..9b3096be8b 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -23,6 +23,7 @@ use Laracasts\Presenter\PresentableTrait; use App\Utils\Traits\CompanySettingsSaver; use Illuminate\Notifications\Notification; use App\Models\Presenters\CompanyPresenter; +use App\Services\Company\CompanyService; use App\Services\Notification\NotificationService; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -974,4 +975,9 @@ class Company extends BaseModel return $this->e_invoice_certificate_passphrase; } + public function service(): CompanyService + { + return new CompanyService($this); + } + } diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index ccb25724ef..21b61b9c32 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -117,7 +117,6 @@ class CompanyGateway extends BaseModel 16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover'], ]; - // const TYPE_PAYPAL = 300; // const TYPE_STRIPE = 301; // const TYPE_LEDGER = 302; @@ -132,6 +131,7 @@ class CompanyGateway extends BaseModel // const TYPE_MOLLIE = 312; // const TYPE_EWAY = 313; // const TYPE_FORTE = 314; + // const PAYPAL_PPCP = 323; public $gateway_consts = [ '38f2c48af60c7dd69e04248cbb24c36e' => 300, @@ -150,6 +150,7 @@ class CompanyGateway extends BaseModel '65faab2ab6e3223dbe848b1686490baz' => 320, 'b9886f9257f0c6ee7c302f1c74475f6c' => 321, 'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9' => 322, + '80af24a6a691230bbec33e930ab40666' => 323, ]; protected $touches = []; diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 681889a68e..d811ea7ed0 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -90,7 +90,7 @@ class Gateway extends StaticModel if ($this->id == 1) { $link = 'http://reseller.authorize.net/application/?id=5560364'; - } elseif ($this->id == 15) { + } elseif (in_array($this->id,[15,60,61])) { $link = 'https://www.paypal.com/us/cgi-bin/webscr?cmd=_login-api-run'; } elseif ($this->id == 24) { $link = 'https://www.2checkout.com/referral?r=2c37ac2298'; @@ -202,7 +202,19 @@ class Gateway extends StaticModel // GatewayType::PRZELEWY24 => ['refund' => false, 'token_billing' => false], // GatewayType::SOFORT => ['refund' => false, 'token_billing' => false], ]; //Paypal - + case 61: + return [ + GatewayType::PAYPAL => ['refund' => false, 'token_billing' => false], + GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false], + GatewayType::VENMO => ['refund' => false, 'token_billing' => false], + // GatewayType::SEPA => ['refund' => false, 'token_billing' => false], + // GatewayType::BANCONTACT => ['refund' => false, 'token_billing' => false], + // GatewayType::EPS => ['refund' => false, 'token_billing' => false], + // GatewayType::MYBANK => ['refund' => false, 'token_billing' => false], + // GatewayType::PAYLATER => ['refund' => false, 'token_billing' => false], + // GatewayType::PRZELEWY24 => ['refund' => false, 'token_billing' => false], + // GatewayType::SOFORT => ['refund' => false, 'token_billing' => false], + ]; //Paypal PPCP default: return []; } diff --git a/app/Models/Presenters/ClientPresenter.php b/app/Models/Presenters/ClientPresenter.php index e9ada6d652..bd3a322e33 100644 --- a/app/Models/Presenters/ClientPresenter.php +++ b/app/Models/Presenters/ClientPresenter.php @@ -110,6 +110,11 @@ class ClientPresenter extends EntityPresenter return $str; } + public function shipping_country_code(): string + { + return $this->entity->shipping_country ? $this->entity->shipping_country->iso_3166_2 : $this->entity->country->iso_3166_2; + } + public function phone() { return $this->entity->phone ?: ''; diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php index 781e2b1a7a..082f5280c2 100644 --- a/app/Models/SystemLog.php +++ b/app/Models/SystemLog.php @@ -148,6 +148,8 @@ class SystemLog extends Model const TYPE_RAZORPAY = 322; + const TYPE_PAYPAL_PPCP = 323; + const TYPE_QUOTA_EXCEEDED = 400; const TYPE_UPSTREAM_FAILURE = 401; diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 243a386913..d6365f7e65 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -13,6 +13,7 @@ namespace App\Models; use App\DataMapper\CompanySettings; use App\Models\Presenters\VendorPresenter; +use App\Services\Vendor\VendorService; use App\Utils\Traits\AppSetup; use App\Utils\Traits\GeneratesCounter; use Illuminate\Database\Eloquent\SoftDeletes; @@ -280,4 +281,9 @@ class Vendor extends BaseModel { return $this->company->company_key.'/'.$this->vendor_hash.'/backups'; } + + public function service() + { + return new VendorService($this); + } } diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index d0f7456450..ad9493865c 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -108,6 +108,11 @@ class BaseDriver extends AbstractPaymentDriver return $this; } + public function getAvailableMethods(): array + { + return self::$methods; + } + /** * Required fields for client to fill, to proceed with gateway actions. * diff --git a/app/PaymentDrivers/CheckoutCom/CreditCard.php b/app/PaymentDrivers/CheckoutCom/CreditCard.php index 5436ce35ba..a55f538556 100644 --- a/app/PaymentDrivers/CheckoutCom/CreditCard.php +++ b/app/PaymentDrivers/CheckoutCom/CreditCard.php @@ -263,6 +263,16 @@ class CreditCard implements MethodInterface if (is_array($error_details)) { $error_details = end($e->error_details['error_codes']); + + SystemLogger::dispatch( + $error_details, + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_ERROR, + SystemLog::TYPE_CHECKOUT, + $this->checkout->client, + $this->checkout->client->company, + ); + } $this->checkout->unWindGatewayFees($this->checkout->payment_hash); @@ -294,7 +304,7 @@ class CreditCard implements MethodInterface ); return new PaymentFailed($e->getMessage(), $e->getCode()); - // return $this->checkout->processInternallyFailedPayment($this->checkout, $human_exception); + } catch (CheckoutAuthorizationException $e) { // Bad Invalid authorization diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 7f4fea2ab7..90aac54f0e 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -415,7 +415,8 @@ class CheckoutComPaymentDriver extends BaseDriver SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_CHECKOUT, - $this->client + $this->client, + $this->client->company, ); return $payment; @@ -436,7 +437,8 @@ class CheckoutComPaymentDriver extends BaseDriver SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_CHECKOUT, - $this->client + $this->client, + $this->client->company ); return false; diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php new file mode 100644 index 0000000000..2684aaa1ce --- /dev/null +++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php @@ -0,0 +1,484 @@ + 'paypal', + 1 => 'card', + 25 => 'venmo', + // 9 => 'sepa', + // 12 => 'bancontact', + // 17 => 'eps', + // 15 => 'giropay', + // 13 => 'ideal', + // 26 => 'mercadopago', + // 27 => 'mybank', + // 28 => 'paylater', + // 16 => 'p24', + // 7 => 'sofort' + ]; + + /** + * Return an array of + * enabled gateway payment methods + * + * @return array + */ + public function gatewayTypes(): array + { + + return collect($this->company_gateway->fees_and_limits) + ->filter(function ($fee){ + return $fee->is_enabled; + })->map(function ($fee, $key){ + return (int)$key; + })->toArray(); + + } + + private function getPaymentMethod($gateway_type_id): int + { + $method = PaymentType::PAYPAL; + + match($gateway_type_id){ + "1" => $method = PaymentType::CREDIT_CARD_OTHER, + "3" => $method = PaymentType::PAYPAL, + "25" => $method = PaymentType::VENMO, + }; + + return $method; + } + + private function getFundingOptions():string + { + + $enums = [ + 1 => 'card', + 3 => 'paypal', + 25 => 'venmo', + // 9 => 'sepa', + // 12 => 'bancontact', + // 17 => 'eps', + // 15 => 'giropay', + // 13 => 'ideal', + // 26 => 'mercadopago', + // 27 => 'mybank', + // 28 => 'paylater', + // 16 => 'p24', + // 7 => 'sofort' + ]; + + $funding_options = ''; + + foreach($this->company_gateway->fees_and_limits as $key => $value) { + + if($value->is_enabled) { + + $funding_options .=$enums[$key].','; + + } + + } + + return rtrim($funding_options, ','); + + } + + /** + * Initialize the Paypal gateway. + * + * Attempt to generate and return the access token. + * + * @return self + */ + public function init(): self + { + + $this->api_endpoint_url = $this->company_gateway->getConfigField('testMode') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; + + $secret = config('ninja.paypal.secret'); + $client_id = config('ninja.paypal.client_id'); + + if($this->access_token && $this->token_expiry && $this->token_expiry->isFuture()) + return $this; + + $response = Http::withBasicAuth($client_id, $secret) + ->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded']) + ->withQueryParameters(['grant_type' => 'client_credentials']) + ->post("{$this->api_endpoint_url}/v1/oauth2/token"); + + if($response->successful()) { + $this->access_token = $response->json()['access_token']; + $this->token_expiry = now()->addSeconds($response->json()['expires_in'] - 60); + } else { + throw new PaymentFailed('Unable to gain access token from Paypal. Check your configuration', 401); + } + + return $this; + + } + + public function setPaymentMethod($payment_method_id) + { + if(!$payment_method_id) { + return $this; + } + + $this->gateway_type_id = $payment_method_id; + + $this->paypal_payment_method = $this->funding_options[$payment_method_id]; + + return $this; + } + + public function authorizeView($payment_method) + { + // PayPal doesn't support direct authorization. + + return $this; + } + + public function authorizeResponse($request) + { + // PayPal doesn't support direct authorization. + + return $this; + } + + private function checkPaymentsReceivable(): self + { + + if($this->company_gateway->getConfigField('status') != 'activated'){ + + if (class_exists(\Modules\Admin\Services\PayPal\PayPalService::class)) { + $pp = new \Modules\Admin\Services\PayPal\PayPalService($this->company_gateway->company, $this->company_gateway->user); + $pp->updateMerchantStatus($this->company_gateway); + + $this->company_gateway = $this->company_gateway->fresh(); + $config = $this->company_gateway->getConfig(); + + if($config->status == 'activated') + return $this; + + } + + throw new PaymentFailed('Unable to accept payments at this time, please contact PayPal for more information.', 401); + } + + return $this; + + } + + public function processPaymentView($data) + { + $this->init()->checkPaymentsReceivable(); + + $data['gateway'] = $this; + $this->payment_hash->data = array_merge((array) $this->payment_hash->data, ['amount' => $data['total']['amount_with_fee']]); + $this->payment_hash->save(); + + $data['client_id'] = config('ninja.paypal.client_id'); + $data['token'] = $this->getClientToken(); + $data['order_id'] = $this->createOrder($data); + $data['funding_source'] = $this->paypal_payment_method; + $data['gateway_type_id'] = $this->gateway_type_id; + $data['merchantId'] = $this->company_gateway->getConfigField('merchantId'); + + // nlog($data['merchantId']); + + return render('gateways.paypal.ppcp.pay', $data); + + } + + private function getClientToken(): string + { + + $r = $this->gatewayRequest('/v1/identity/generate-token', 'post', ['body' => '']); + + if($r->successful()) + return $r->json()['client_token']; + + throw new PaymentFailed('Unable to gain client token from Paypal. Check your configuration', 401); + + } + + public function processPaymentResponse($request) + { + + $request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']); + $response = json_decode($request['gateway_response'], true); + + if(isset($response['status']) && $response['status'] == 'COMPLETED' && isset($response['purchase_units'])) { + + $data = [ + 'payment_type' => $this->getPaymentMethod($request->gateway_type_id), + 'amount' => $response['purchase_units'][0]['amount']['value'], + 'transaction_reference' => $response['purchase_units'][0]['payments']['captures'][0]['id'], + 'gateway_type_id' => GatewayType::PAYPAL, + ]; + + $payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED); + + SystemLogger::dispatch( + ['response' => $response, 'data' => $data], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_PAYPAL, + $this->client, + $this->client->company, + ); + + return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); + + } else { + + if(isset($response['headers']) ?? false) + unset($response['headers']); + + SystemLogger::dispatch( + ['response' => $response], + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_PAYPAL, + $this->client, + $this->client->company, + ); + + $message = $response['body']['details'][0]['description'] ?? 'Payment failed. Please try again.'; + + throw new PaymentFailed($message, 400); + } + } + + private function paymentSource(): array + { + /** we only need to support paypal as payment source until as we are only using hosted payment buttons */ + return $this->injectPayPalPaymentSource(); + + } + + /**@deprecated v5.7.54 */ + private function injectVenmoPaymentSource(): array + { + + return [ + "venmo" => [ + "email_address" => $this->client->present()->email(), + ], + ]; + + } + + /**@deprecated v5.7.54 */ + private function injectCardPaymentSource(): array + { + + return [ + "card" => [ + + "name" => $this->client->present()->name(), + "email_address" => $this->client->present()->email(), + "billing_address" => $this->getBillingAddress(), + "experience_context"=> [ + "user_action" => "PAY_NOW" + ], + ], + ]; + + } + + private function injectPayPalPaymentSource(): array + { + + return [ + "paypal" => [ + + "name" => [ + "given_name" => $this->client->present()->first_name(), + "surname" => $this->client->present()->last_name(), + ], + "email_address" => $this->client->present()->email(), + "address" => $this->getBillingAddress(), + "experience_context"=> [ + "user_action" => "PAY_NOW" + ], + ], + ]; + + } + + private function createOrder(array $data): string + { + + $_invoice = collect($this->payment_hash->data->invoices)->first(); + + $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id)); + + $description = collect($invoice->line_items)->map(function ($item){ + return $item->notes; + })->implode("\n"); + + $order = [ + + "intent" => "CAPTURE", + "payment_source" => $this->paymentSource(), + "purchase_units" => [ + [ + "description" =>ctrans('texts.invoice_number').'# '.$invoice->number, + "invoice_id" => $invoice->number, + "payee" => [ + "merchant_id" => $this->company_gateway->getConfigField('merchantId'), + ], + "payment_instruction" => [ + "disbursement_mode" => "INSTANT", + ], + $this->getShippingAddress(), + "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, + "description" => substr($description, 0, 127), + "quantity" => "1", + "unit_amount" => [ + "currency_code" => $this->client->currency()->code, + "value" => (string)$data['amount_with_fee'] + ], + ], + ], + ], + ] + ]; + + + if($shipping = $this->getShippingAddress()){ + $order['purchase_units'][0] = $shipping; + } + + $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); + + nlog($r->json()); + + return $r->json()['id']; + + } + + private function getBillingAddress(): array + { + return + [ + "address_line_1" => $this->client->address1, + "address_line_2" => $this->client->address2, + "admin_area_2" => $this->client->city, + "admin_area_1" => $this->client->state, + "postal_code" => $this->client->postal_code, + "country_code" => $this->client->country->iso_3166_2, + ]; + } + + private function getShippingAddress(): ?array + { + return $this->company_gateway->require_shipping_address ? + [ + "shipping" => [ + "address" => + [ + "address_line_1" => $this->client->shipping_address1, + "address_line_2" => $this->client->shipping_address2, + "admin_area_2" => $this->client->shipping_city, + "admin_area_1" => $this->client->shipping_state, + "postal_code" => $this->client->shipping_postal_code, + "country_code" => $this->client->present()->shipping_country_code(), + ], + ] + ] + : null; + + } + + public function gatewayRequest(string $uri, string $verb, array $data, ?array $headers = []) + { + $this->init(); + + $r = Http::withToken($this->access_token) + ->withHeaders($this->getHeaders($headers)) + ->{$verb}("{$this->api_endpoint_url}{$uri}", $data); + + if($r->successful()) { + return $r; + } + + throw new PaymentFailed("Gateway failure - {$r->body()}", 401); + + } + + private function getHeaders(array $headers = []): array + { + return array_merge([ + 'Accept' => 'application/json', + 'Content-type' => 'application/json', + 'Accept-Language' => 'en_US', + 'PayPal-Partner-Attribution-Id' => 'invoiceninja_SP_PPCP', + 'PayPal-Request-Id' => Str::uuid()->toString(), + ], $headers); + } + + private function feeCalc($invoice, $invoice_total) + { + + } +} \ No newline at end of file diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index 336e04f130..d0395ee6b5 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -140,13 +140,13 @@ class StripePaymentDriver extends BaseDriver return $this; } + /** * Returns the gateway types. */ public function gatewayTypes(): array { $types = [ - // GatewayType::CRYPTO, GatewayType::CREDIT_CARD, ]; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 8c4448b230..6014514236 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -182,7 +182,6 @@ use App\Listeners\Mail\MailSentListener; use App\Listeners\Misc\InvitationViewedListener; use App\Listeners\Payment\PaymentBalanceActivity; use App\Listeners\Payment\PaymentEmailedActivity; -use App\Listeners\Payment\PaymentEmailFailureActivity; use App\Listeners\Payment\PaymentNotification; use App\Listeners\Payment\PaymentRestoredActivity; use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; @@ -281,6 +280,13 @@ class EventServiceProvider extends ServiceProvider * */ protected $listen = [ + + // RequestSending::class => [ + // LogRequestSending::class, + // ], + // ResponseReceived::class => [ + // LogResponseReceived::class, + // ], AccountCreated::class => [ ], MessageSending::class => [ diff --git a/app/Repositories/ExpenseRepository.php b/app/Repositories/ExpenseRepository.php index d71006e9bb..307ccdaeec 100644 --- a/app/Repositories/ExpenseRepository.php +++ b/app/Repositories/ExpenseRepository.php @@ -18,9 +18,10 @@ use App\Factory\ExpenseFactory; use App\Models\ExpenseCategory; use App\Utils\Traits\GeneratesCounter; use Illuminate\Database\QueryException; +use App\Jobs\Expense\VendorExpenseNotify; +use Illuminate\Database\Eloquent\Collection; use Carbon\Exceptions\InvalidFormatException; use App\Libraries\Currency\Conversion\CurrencyApi; -use Illuminate\Database\Eloquent\Collection; /** * ExpenseRepository. @@ -31,6 +32,7 @@ class ExpenseRepository extends BaseRepository private $completed = true; + private $notify_vendor = false; /** * Saves the expense and its contacts. * @@ -41,10 +43,15 @@ class ExpenseRepository extends BaseRepository */ public function save(array $data, Expense $expense): Expense { + /** @var \App\Models\User $user */ + $user = auth()->user(); + if(isset($data['payment_date']) && $data['payment_date'] == $expense->payment_date) { //do nothing - } elseif(isset($data['payment_date']) && strlen($data['payment_date']) > 1 && $expense->company->notify_vendor_when_paid) { - nlog("NOT SAME"); + } + elseif(isset($data['payment_date']) && strlen($data['payment_date']) > 1 && $user->company()->notify_vendor_when_paid && ($data['vendor_id'] || $expense->vendor_id)) { + nlog("ping"); + $this->notify_vendor = true; } $expense->fill($data); @@ -63,6 +70,9 @@ class ExpenseRepository extends BaseRepository $this->saveDocuments($data['documents'], $expense); } + if($this->notify_vendor) + VendorExpenseNotify::dispatch($expense, $expense->company->db); + return $expense; } diff --git a/app/Services/Company/CompanyService.php b/app/Services/Company/CompanyService.php new file mode 100644 index 0000000000..49d8cb5cb7 --- /dev/null +++ b/app/Services/Company/CompanyService.php @@ -0,0 +1,76 @@ +company->settings->country_id) { + + case '36': // Australia + $taxes[] = ['name' => 'GST', 'rate' => 10]; + break; + case '40': // Austria + $taxes[] = ['name' => 'USt', 'rate' => 20]; + break; + case '56': // Belgium + $taxes[] = ['name' => 'BTW', 'rate' => 21]; + break; + case '100': // Bulgaria + $taxes[] = ['name' => 'ДДС', 'rate' => 20]; + break; + case '250': // France + $taxes[] = ['name' => 'TVA', 'rate' => 20]; + break; + case '276': // Germany + $taxes[] = ['name' => 'MwSt', 'rate' => 19]; + break; + case '554': // New Zealand + $taxes[] = ['name' => 'GST', 'rate' => 15]; + break; + case '710': // South Africa + $taxes[] = ['name' => 'VAT', 'rate' => 15]; + break; + case '724': // Spain + $taxes[] = ['name' => 'IVA', 'rate' => 21]; + break; + + default: + return; + } + + foreach($taxes as $tax) { + $tax_rate = TaxRateFactory::create($this->company->id, $user->id); + $tax_rate->fill($tax); + $tax_rate->save(); + } + + }catch(\Exception $e){ + nlog($e->getMessage()); + } + + } + +} \ No newline at end of file diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php index 0484b5228a..35413125db 100644 --- a/app/Services/Email/EmailDefaults.php +++ b/app/Services/Email/EmailDefaults.php @@ -166,7 +166,6 @@ class EmailDefaults if (strlen($this->email->email_object->body) > 3) { // A Custom Message has been set in the email screen. - // return $this; } elseif (strlen($this->email->email_object->settings?->{$this->email->email_object->email_template_body}) > 3) { // A body has been saved in the settings. $this->email->email_object->body = $this->email->email_object->settings?->{$this->email->email_object->email_template_body}; diff --git a/app/Services/Vendor/VendorService.php b/app/Services/Vendor/VendorService.php new file mode 100644 index 0000000000..100438957f --- /dev/null +++ b/app/Services/Vendor/VendorService.php @@ -0,0 +1,21 @@ +=7.1.3", - "symfony/intl": "^5.4|^6.0", - "twig/twig": "^2.7|^3.0" + "php": ">=7.2.5", + "symfony/intl": "^5.4|^6.0|^7.0", + "twig/twig": "^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.4|^6.3" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -13567,7 +13566,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.7.1" + "source": "https://github.com/twigphp/intl-extra/tree/v3.8.0" }, "funding": [ { @@ -13579,30 +13578,31 @@ "type": "tidelift" } ], - "time": "2023-07-29T15:34:56+00:00" + "time": "2023-11-21T17:27:48+00:00" }, { "name": "twig/twig", - "version": "v3.7.1", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3" + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" }, "type": "library", "autoload": { @@ -13638,7 +13638,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.1" + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" }, "funding": [ { @@ -13650,7 +13650,7 @@ "type": "tidelift" } ], - "time": "2023-08-28T11:09:02+00:00" + "time": "2023-11-21T18:54:41+00:00" }, { "name": "twilio/sdk", @@ -14825,16 +14825,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.38.0", + "version": "v3.39.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "7e6070026e76aa09d77a47519625c86593fb8e31" + "reference": "857046d26b0d92dc13c4be769309026b100b517e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7e6070026e76aa09d77a47519625c86593fb8e31", - "reference": "7e6070026e76aa09d77a47519625c86593fb8e31", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/857046d26b0d92dc13c4be769309026b100b517e", + "reference": "857046d26b0d92dc13c4be769309026b100b517e", "shasum": "" }, "require": { @@ -14844,16 +14844,16 @@ "ext-tokenizer": "*", "php": "^7.4 || ^8.0", "sebastian/diff": "^4.0 || ^5.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/options-resolver": "^5.4 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", "symfony/polyfill-mbstring": "^1.27", "symfony/polyfill-php80": "^1.27", "symfony/polyfill-php81": "^1.27", - "symfony/process": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { "facile-it/paraunit": "^1.3 || ^2.0", @@ -14867,8 +14867,8 @@ "phpspec/prophecy": "^1.16", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", - "symfony/phpunit-bridge": "^6.2.3", - "symfony/yaml": "^5.4 || ^6.0" + "symfony/phpunit-bridge": "^6.2.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -14906,7 +14906,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.38.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.39.1" }, "funding": [ { @@ -14914,7 +14914,7 @@ "type": "github" } ], - "time": "2023-11-07T08:44:54+00:00" + "time": "2023-11-24T22:59:03+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -15628,16 +15628,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.41", + "version": "1.10.44", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c6174523c2a69231df55bdc65b61655e72876d76" + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6174523c2a69231df55bdc65b61655e72876d76", - "reference": "c6174523c2a69231df55bdc65b61655e72876d76", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bf84367c53a23f759513985c54ffe0d0c249825b", + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b", "shasum": "" }, "require": { @@ -15686,20 +15686,20 @@ "type": "tidelift" } ], - "time": "2023-11-05T12:57:57+00:00" + "time": "2023-11-21T16:30:46+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.7", + "version": "10.1.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "355324ca4980b8916c18b9db29f3ef484078f26e" + "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/355324ca4980b8916c18b9db29f3ef484078f26e", - "reference": "355324ca4980b8916c18b9db29f3ef484078f26e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a56a9ab2f680246adcf3db43f38ddf1765774735", + "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735", "shasum": "" }, "require": { @@ -15756,7 +15756,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.9" }, "funding": [ { @@ -15764,7 +15764,7 @@ "type": "github" } ], - "time": "2023-10-04T15:34:17+00:00" + "time": "2023-11-23T12:23:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -17537,16 +17537,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -17575,7 +17575,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -17583,7 +17583,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], diff --git a/config/ninja.php b/config/ninja.php index be246ea105..3f48fd1f53 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -86,6 +86,7 @@ return [ 'password' => 'password', 'stripe' => env('STRIPE_KEYS', ''), 'paypal' => env('PAYPAL_KEYS', ''), + 'ppcp' => env('PPCP_KEYS', ''), 'paypal_rest' => env('PAYPAL_REST_KEYS', ''), 'authorize' => env('AUTHORIZE_KEYS', ''), 'checkout' => env('CHECKOUT_KEYS', ''), @@ -223,4 +224,8 @@ return [ 'client_id' => env('SHOPIFY_CLIENT_ID', null), 'client_secret' => env('SHOPIFY_CLIENT_SECRET', null), ], + 'paypal' => [ + 'secret' => env('PAYPAL_SECRET', null), + 'client_id' => env('PAYPAL_CLIENT_ID', null), + ] ]; diff --git a/database/migrations/2023_10_15_204204_add_paypal_ppcp.php b/database/migrations/2023_10_15_204204_add_paypal_ppcp.php new file mode 100644 index 0000000000..2fdee3ea77 --- /dev/null +++ b/database/migrations/2023_10_15_204204_add_paypal_ppcp.php @@ -0,0 +1,49 @@ +clientId = ""; + $fields->secret = ""; + $fields->testMode = false; + + $paypal = new Gateway; + $paypal->id = 61; + $paypal->name = 'PayPal Platform'; + $paypal->key = '80af24a6a691230bbec33e930ab40666'; + $paypal->provider = 'PayPal_PPCP'; + $paypal->is_offsite = false; + $paypal->fields = \json_encode($fields); + $paypal->visible = 1; + $paypal->site_url = 'https://www.paypal.com/'; + $paypal->save(); + + } + + Gateway::whereIn('id', [60, 15, 49])->update(['visible' => 0]); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/seeders/PaymentLibrariesSeeder.php b/database/seeders/PaymentLibrariesSeeder.php index 6803c4bf5f..ef83fddaf2 100644 --- a/database/seeders/PaymentLibrariesSeeder.php +++ b/database/seeders/PaymentLibrariesSeeder.php @@ -84,6 +84,7 @@ class PaymentLibrariesSeeder extends Seeder ['id' => 58, 'name' => 'Razorpay', 'provider' => 'Razorpay', 'is_offsite' => false, 'sort_order' => 21, 'key' => 'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9', 'fields' => '{"apiKey":"","apiSecret":""}'], ['id' => 59, 'name' => 'Forte', 'provider' => 'Forte', 'is_offsite' => false, 'sort_order' => 21, 'key' => 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs', 'fields' => '{"testMode":false,"apiLoginId":"","apiAccessId":"","secureKey":"","authOrganizationId":"","organizationId":"","locationId":""}'], ['id' => 60, 'name' => 'PayPal REST', 'provider' => 'PayPal_Rest', 'key' => '80af24a6a691230bbec33e930ab40665', 'fields' => '{"clientId":"","secret":"","signature":"","testMode":false}'], + ['id' => 61, 'name' => 'PayPal Platform', 'provider' => 'PayPal_PPCP', 'key' => '80af24a6a691230bbec33e930ab40666', 'fields' => '{"testMode":false}'], ]; foreach ($gateways as $gateway) { @@ -103,9 +104,8 @@ class PaymentLibrariesSeeder extends Seeder Gateway::whereIn('id', [1,3,7,11,15,20,39,46,55,50,57,52,58,59,60])->update(['visible' => 1]); if (Ninja::isHosted()) { - Gateway::whereIn('id', [20])->update(['visible' => 0]); - Gateway::whereIn('id', [56])->update(['visible' => 1]); - Gateway::whereIn('id', [49])->update(['visible' => 1]); + Gateway::whereIn('id', [20,49])->update(['visible' => 0]); + Gateway::whereIn('id', [56,61])->update(['visible' => 1]); } Gateway::all()->each(function ($gateway) { diff --git a/lang/en/texts.php b/lang/en/texts.php index 64be06692e..d1a4d812ad 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5213,6 +5213,10 @@ $LANG = array( 'payment_email_all_contacts' => 'Payment Email To All Contacts', 'payment_email_all_contacts_help' => 'Sends the payment email to all contacts when enabled', 'add_line' => 'Add Line', + 'activity_139' => 'Expense :expense notification sent to :contact', + 'vendor_notification_subject' => 'Confirmation of payment :amount sent to :vendor', + 'vendor_notification_body' => 'Payment processed for :amount dated :payment_date.
[Transaction Reference: :transaction_reference]', + ); return $LANG; diff --git a/resources/views/portal/ninja2020/gateways/paypal/ppcp/pay.blade.php b/resources/views/portal/ninja2020/gateways/paypal/ppcp/pay.blade.php new file mode 100644 index 0000000000..508d228586 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/paypal/ppcp/pay.blade.php @@ -0,0 +1,69 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' => 'PayPal']) + +@section('gateway_head') + + +@endsection + +@section('gateway_content') +
+ @csrf + + + + + +
+ + + +
+ +@endsection + +@section('gateway_footer') +@endsection + +@push('footer') + + +
+ + +@endpush \ No newline at end of file diff --git a/tests/Feature/CompanyTest.php b/tests/Feature/CompanyTest.php index 6cfc427d92..6204e86ad8 100644 --- a/tests/Feature/CompanyTest.php +++ b/tests/Feature/CompanyTest.php @@ -11,18 +11,19 @@ namespace Tests\Feature; -use App\DataMapper\CompanySettings; -use App\Http\Middleware\PasswordProtection; +use Tests\TestCase; use App\Models\Company; +use App\Models\TaxRate; +use Tests\MockAccountData; use App\Models\CompanyToken; use App\Utils\Traits\MakesHash; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Http\UploadedFile; +use App\DataMapper\CompanySettings; use Illuminate\Support\Facades\Log; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Session; -use Tests\MockAccountData; -use Tests\TestCase; +use App\Http\Middleware\PasswordProtection; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -49,6 +50,18 @@ class CompanyTest extends TestCase $this->makeTestData(); } + public function testCompanyTaxInit() + { + TaxRate::query()->delete(); + + $settings = $this->company->settings; + $settings->country_id = '40'; + $this->company->saveSettings($settings, $this->company); + + $this->company->service()->localizeCompany($this->user); + + $this->assertEquals(1, TaxRate::count()); + } public function testCompanyLogoInline() {