mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-15 23:52:33 +01:00
commit
beeb19d6a2
2
.env.ci
2
.env.ci
@ -5,7 +5,7 @@ APP_DEBUG=true
|
|||||||
APP_URL=http://ninja.test
|
APP_URL=http://ninja.test
|
||||||
MULTI_DB_ENABLED=false
|
MULTI_DB_ENABLED=false
|
||||||
# database
|
# database
|
||||||
DB_CONNECTION=db-ninja-01
|
DB_CONNECTION=mysql
|
||||||
DB_DATABASE1=ninja
|
DB_DATABASE1=ninja
|
||||||
DB_USERNAME1=root
|
DB_USERNAME1=root
|
||||||
DB_PASSWORD1=ninja
|
DB_PASSWORD1=ninja
|
||||||
|
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,6 +1,16 @@
|
|||||||
# Release notes
|
# Release notes
|
||||||
|
|
||||||
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
|
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
|
||||||
|
## Added:
|
||||||
|
- Client portal: Show message when trying to approve non-approvable quotes
|
||||||
|
- Client portal: Remove "Approve" button from single quote page if quote is non-approvable
|
||||||
|
- Client portal: Hide "Pay now" buttons if no gateways are configured
|
||||||
|
- Client portal: "Download" and "Open in new tab" buttons on documents show page
|
||||||
|
- Client portal: Make "Invoice Ninja" link clickable in footer
|
||||||
|
|
||||||
|
## Fixed:
|
||||||
|
- Client portal: Showing message instead of blank page when trying to download zero quotes
|
||||||
|
- Client portal: Fixed bug with payment gateways after checking for required fields
|
||||||
|
|
||||||
## [v5.2.0-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.2.0-release)
|
## [v5.2.0-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.2.0-release)
|
||||||
## Added:
|
## Added:
|
||||||
|
31
README.md
31
README.md
@ -6,15 +6,30 @@
|
|||||||
![v5-stable phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-stable)
|
![v5-stable phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-stable)
|
||||||
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/d16c78aad8574466bf83232b513ef4fb)](https://www.codacy.com/gh/turbo124/invoiceninja/dashboard?utm_source=github.com&utm_medium=referral&utm_content=turbo124/invoiceninja&utm_campaign=Badge_Grade)
|
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/d16c78aad8574466bf83232b513ef4fb)](https://www.codacy.com/gh/turbo124/invoiceninja/dashboard?utm_source=github.com&utm_medium=referral&utm_content=turbo124/invoiceninja&utm_campaign=Badge_Grade)
|
||||||
|
|
||||||
# Invoice Ninja version 5!
|
# Invoice Ninja 5
|
||||||
|
|
||||||
## Preamble
|
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org)
|
||||||
|
|
||||||
|
### We're on Slack, join us at [slack.invoiceninja.com](http://slack.invoiceninja.com), [forum.invoiceninja.com](https://forum.invoiceninja.com) or if you like [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/)
|
||||||
|
|
||||||
|
Just make sure to add the `invoice-ninja` tag to your question.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
Version 5 of Invoice Ninja is here! We've taken the best parts of version 4 and bolted on all of the most requested features to produce a invoicing application like no other.
|
Version 5 of Invoice Ninja is here! We've taken the best parts of version 4 and bolted on all of the most requested features to produce a invoicing application like no other.
|
||||||
|
|
||||||
The new interface has a lot more functionality so it isn't a carbon copy of v4, but once you get used to the new layout and functionality we are sure you will love it!
|
The new interface has a lot more functionality so it isn't a carbon copy of v4, but once you get used to the new layout and functionality we are sure you will love it!
|
||||||
|
|
||||||
If you have any questions, please join us on our [forum](https://forum.invoiceninja.com) or on [slack](https://invoiceninja.slack.com)
|
## Referral Program
|
||||||
|
* Earn 50% of Pro & Enterprise Plans up to 4 years - [Learn more](https://www.invoiceninja.com/referral-program/)
|
||||||
|
|
||||||
|
## Recommended Providers
|
||||||
|
* [Stripe](https://stripe.com/)
|
||||||
|
* [Postmark](https://postmarkapp.com/)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
* [API Documentation](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
|
||||||
|
* [APP Documentation](https://invoiceninja.github.io/)
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@ -90,16 +105,6 @@ Installation, Configuration and Troubleshooting documentation can be found [HERE
|
|||||||
* [Clemens Mol](https://github.com/clemensmol)
|
* [Clemens Mol](https://github.com/clemensmol)
|
||||||
* [Benjamin Beganović](https://github.com/beganovich)
|
* [Benjamin Beganović](https://github.com/beganovich)
|
||||||
|
|
||||||
## Current work in progress
|
|
||||||
|
|
||||||
Invoice Ninja is written in a combination of technologies:
|
|
||||||
|
|
||||||
API - Laravel
|
|
||||||
Client Portal - Laravel + Tailwind
|
|
||||||
Admin Portal - Flutter
|
|
||||||
|
|
||||||
This allows an immersive and consistent experience across any device: mobile, tablet or desktop.
|
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
If you find a security issue with this application please send an email to contact@invoiceninja.com Please follow responsible disclosure procedures if you detect an issue. For further information on responsible disclosure please read [here](https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html)
|
If you find a security issue with this application please send an email to contact@invoiceninja.com Please follow responsible disclosure procedures if you detect an issue. For further information on responsible disclosure please read [here](https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html)
|
||||||
|
@ -1 +1 @@
|
|||||||
5.2.11
|
5.2.13
|
@ -69,7 +69,7 @@ class CheckData extends Command
|
|||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=} {--paid_to_date=}';
|
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=} {--paid_to_date=} {--client_balance=}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -506,6 +506,7 @@ class CheckData extends Command
|
|||||||
$this->logMessage($client->present()->name.' - '.$client->id." - calculated client balances do not match Invoice Balances = {$invoice_balance} - Client Balance = ".rtrim($client->balance, '0'). " Ledger balance = {$ledger->balance}");
|
$this->logMessage($client->present()->name.' - '.$client->id." - calculated client balances do not match Invoice Balances = {$invoice_balance} - Client Balance = ".rtrim($client->balance, '0'). " Ledger balance = {$ledger->balance}");
|
||||||
|
|
||||||
$this->isValid = false;
|
$this->isValid = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,6 +536,20 @@ class CheckData extends Command
|
|||||||
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." - Balance Failure - Invoice Balances = {$invoice_balance} Client Balance = {$client->balance} Ledger Balance = {$ledger->balance}");
|
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." - Balance Failure - Invoice Balances = {$invoice_balance} Client Balance = {$client->balance} Ledger Balance = {$ledger->balance}");
|
||||||
|
|
||||||
$this->isValid = false;
|
$this->isValid = false;
|
||||||
|
|
||||||
|
|
||||||
|
if($this->option('client_balance')){
|
||||||
|
|
||||||
|
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." Fixing {$client->balance} to {$invoice_balance}");
|
||||||
|
$client->balance = $invoice_balance;
|
||||||
|
$client->save();
|
||||||
|
|
||||||
|
$ledger->adjustment = $invoice_balance;
|
||||||
|
$ledger->balance = $invoice_balance;
|
||||||
|
$ledger->notes = 'Ledger Adjustment';
|
||||||
|
$ledger->save();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,11 @@ namespace App\Console\Commands;
|
|||||||
use App\DataMapper\CompanySettings;
|
use App\DataMapper\CompanySettings;
|
||||||
use App\DataMapper\FeesAndLimits;
|
use App\DataMapper\FeesAndLimits;
|
||||||
use App\Events\Invoice\InvoiceWasCreated;
|
use App\Events\Invoice\InvoiceWasCreated;
|
||||||
|
use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
|
||||||
use App\Factory\GroupSettingFactory;
|
use App\Factory\GroupSettingFactory;
|
||||||
use App\Factory\InvoiceFactory;
|
use App\Factory\InvoiceFactory;
|
||||||
use App\Factory\InvoiceItemFactory;
|
use App\Factory\InvoiceItemFactory;
|
||||||
|
use App\Factory\RecurringInvoiceFactory;
|
||||||
use App\Factory\SubscriptionFactory;
|
use App\Factory\SubscriptionFactory;
|
||||||
use App\Helpers\Invoice\InvoiceSum;
|
use App\Helpers\Invoice\InvoiceSum;
|
||||||
use App\Jobs\Company\CreateCompanyTaskStatuses;
|
use App\Jobs\Company\CreateCompanyTaskStatuses;
|
||||||
@ -48,6 +50,7 @@ use Illuminate\Console\Command;
|
|||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
class CreateSingleAccount extends Command
|
class CreateSingleAccount extends Command
|
||||||
{
|
{
|
||||||
@ -225,16 +228,19 @@ class CreateSingleAccount extends Command
|
|||||||
|
|
||||||
$client = $company->clients->random();
|
$client = $company->clients->random();
|
||||||
|
|
||||||
$this->info('creating task for client #'.$client->id);
|
$this->info('creating task for client #' . $client->id);
|
||||||
$this->createTask($client);
|
$this->createTask($client);
|
||||||
|
|
||||||
$client = $company->clients->random();
|
$client = $company->clients->random();
|
||||||
|
|
||||||
$this->info('creating project for client #'.$client->id);
|
$this->info('creating project for client #' . $client->id);
|
||||||
$this->createProject($client);
|
$this->createProject($client);
|
||||||
|
|
||||||
$this->info('creating credit for client #'.$client->id);
|
$this->info('creating credit for client #' . $client->id);
|
||||||
$this->createCredit($client);
|
$this->createCredit($client);
|
||||||
|
|
||||||
|
$this->info('creating recurring invoice for client # ' . $client->id);
|
||||||
|
$this->createRecurringInvoice($client);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->createGateways($company, $user);
|
$this->createGateways($company, $user);
|
||||||
@ -249,34 +255,34 @@ class CreateSingleAccount extends Command
|
|||||||
$gs->save();
|
$gs->save();
|
||||||
|
|
||||||
$p1 = Product::factory()->create([
|
$p1 = Product::factory()->create([
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'company_id' => $company->id,
|
'company_id' => $company->id,
|
||||||
'product_key' => 'pro_plan',
|
'product_key' => 'pro_plan',
|
||||||
'notes' => 'The Pro Plan',
|
'notes' => 'The Pro Plan',
|
||||||
'cost' => 10,
|
'cost' => 10,
|
||||||
'price' => 10,
|
'price' => 10,
|
||||||
'quantity' => 1,
|
'quantity' => 1,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$p2 = Product::factory()->create([
|
$p2 = Product::factory()->create([
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'company_id' => $company->id,
|
'company_id' => $company->id,
|
||||||
'product_key' => 'enterprise_plan',
|
'product_key' => 'enterprise_plan',
|
||||||
'notes' => 'The Enterprise Plan',
|
'notes' => 'The Enterprise Plan',
|
||||||
'cost' => 14,
|
'cost' => 14,
|
||||||
'price' => 14,
|
'price' => 14,
|
||||||
'quantity' => 1,
|
'quantity' => 1,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$p3 = Product::factory()->create([
|
$p3 = Product::factory()->create([
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'company_id' => $company->id,
|
'company_id' => $company->id,
|
||||||
'product_key' => 'free_plan',
|
'product_key' => 'free_plan',
|
||||||
'notes' => 'The Free Plan',
|
'notes' => 'The Free Plan',
|
||||||
'cost' => 0,
|
'cost' => 0,
|
||||||
'price' => 0,
|
'price' => 0,
|
||||||
'quantity' => 1,
|
'quantity' => 1,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$webhook_config = [
|
$webhook_config = [
|
||||||
'post_purchase_url' => 'http://ninja.test:8000/api/admin/plan',
|
'post_purchase_url' => 'http://ninja.test:8000/api/admin/plan',
|
||||||
@ -435,6 +441,10 @@ class CreateSingleAccount extends Command
|
|||||||
|
|
||||||
$invoice = $invoice_calc->getInvoice();
|
$invoice = $invoice_calc->getInvoice();
|
||||||
|
|
||||||
|
if ($this->gateway === 'braintree') {
|
||||||
|
$invoice->amount = 100; // Braintree sandbox only allows payments under 2,000 to complete successfully.
|
||||||
|
}
|
||||||
|
|
||||||
$invoice->save();
|
$invoice->save();
|
||||||
$invoice->service()->createInvitations()->markSent();
|
$invoice->service()->createInvitations()->markSent();
|
||||||
|
|
||||||
@ -619,7 +629,7 @@ class CreateSingleAccount extends Command
|
|||||||
|
|
||||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||||
|
|
||||||
$fees_and_limits = new \stdClass;
|
$fees_and_limits = new stdClass;
|
||||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||||
|
|
||||||
$cg->fees_and_limits = $fees_and_limits;
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
@ -642,7 +652,7 @@ class CreateSingleAccount extends Command
|
|||||||
|
|
||||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||||
|
|
||||||
$fees_and_limits = new \stdClass;
|
$fees_and_limits = new stdClass;
|
||||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||||
|
|
||||||
$cg->fees_and_limits = $fees_and_limits;
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
@ -663,7 +673,7 @@ class CreateSingleAccount extends Command
|
|||||||
|
|
||||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||||
|
|
||||||
$fees_and_limits = new \stdClass;
|
$fees_and_limits = new stdClass;
|
||||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||||
|
|
||||||
$cg->fees_and_limits = $fees_and_limits;
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
@ -684,11 +694,96 @@ class CreateSingleAccount extends Command
|
|||||||
|
|
||||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||||
|
|
||||||
$fees_and_limits = new \stdClass;
|
$fees_and_limits = new stdClass;
|
||||||
|
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||||
|
|
||||||
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
|
$cg->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config('ninja.testvars.wepay') && ($this->gateway == 'all' || $this->gateway == 'wepay')) {
|
||||||
|
$cg = new CompanyGateway;
|
||||||
|
$cg->company_id = $company->id;
|
||||||
|
$cg->user_id = $user->id;
|
||||||
|
$cg->gateway_key = '8fdeed552015b3c7b44ed6c8ebd9e992';
|
||||||
|
$cg->require_cvv = true;
|
||||||
|
$cg->require_billing_address = true;
|
||||||
|
$cg->require_shipping_address = true;
|
||||||
|
$cg->update_details = true;
|
||||||
|
$cg->config = encrypt(config('ninja.testvars.wepay'));
|
||||||
|
$cg->save();
|
||||||
|
|
||||||
|
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||||
|
|
||||||
|
$fees_and_limits = new stdClass;
|
||||||
|
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||||
|
|
||||||
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
|
$cg->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config('ninja.testvars.braintree') && ($this->gateway == 'all' || $this->gateway == 'braintree')) {
|
||||||
|
$cg = new CompanyGateway;
|
||||||
|
$cg->company_id = $company->id;
|
||||||
|
$cg->user_id = $user->id;
|
||||||
|
$cg->gateway_key = 'f7ec488676d310683fb51802d076d713';
|
||||||
|
$cg->require_cvv = true;
|
||||||
|
$cg->require_billing_address = true;
|
||||||
|
$cg->require_shipping_address = true;
|
||||||
|
$cg->update_details = true;
|
||||||
|
$cg->config = encrypt(config('ninja.testvars.braintree'));
|
||||||
|
$cg->save();
|
||||||
|
|
||||||
|
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||||
|
|
||||||
|
$fees_and_limits = new stdClass;
|
||||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||||
|
|
||||||
$cg->fees_and_limits = $fees_and_limits;
|
$cg->fees_and_limits = $fees_and_limits;
|
||||||
$cg->save();
|
$cg->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function createRecurringInvoice($client)
|
||||||
|
{
|
||||||
|
$faker = Factory::create();
|
||||||
|
|
||||||
|
$invoice = RecurringInvoiceFactory::create($client->company->id, $client->user->id); //stub the company and user_id
|
||||||
|
$invoice->client_id = $client->id;
|
||||||
|
$dateable = Carbon::now()->subDays(rand(0, 90));
|
||||||
|
$invoice->date = $dateable;
|
||||||
|
|
||||||
|
$invoice->line_items = $this->buildLineItems(rand(1, 10));
|
||||||
|
$invoice->uses_inclusive_taxes = false;
|
||||||
|
|
||||||
|
if (rand(0, 1)) {
|
||||||
|
$invoice->tax_name1 = 'GST';
|
||||||
|
$invoice->tax_rate1 = 10.00;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rand(0, 1)) {
|
||||||
|
$invoice->tax_name2 = 'VAT';
|
||||||
|
$invoice->tax_rate2 = 17.50;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rand(0, 1)) {
|
||||||
|
$invoice->tax_name3 = 'CA Sales Tax';
|
||||||
|
$invoice->tax_rate3 = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice->custom_value1 = $faker->date;
|
||||||
|
$invoice->custom_value2 = rand(0, 1) ? 'yes' : 'no';
|
||||||
|
|
||||||
|
$invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||||
|
$invoice->save();
|
||||||
|
|
||||||
|
$invoice_calc = new InvoiceSum($invoice);
|
||||||
|
$invoice_calc->build();
|
||||||
|
|
||||||
|
$invoice = $invoice_calc->getInvoice();
|
||||||
|
|
||||||
|
$invoice->save();
|
||||||
|
|
||||||
|
event(new RecurringInvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class ExpenseCategoryFactory
|
|||||||
$expense->company_id = $company_id;
|
$expense->company_id = $company_id;
|
||||||
$expense->name = '';
|
$expense->name = '';
|
||||||
$expense->is_deleted = false;
|
$expense->is_deleted = false;
|
||||||
$expense->color = '#fff';
|
$expense->color = '';
|
||||||
|
|
||||||
return $expense;
|
return $expense;
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,8 @@ class RecurringInvoiceToInvoiceFactory
|
|||||||
$invoice->tax_rate2 = $recurring_invoice->tax_rate2;
|
$invoice->tax_rate2 = $recurring_invoice->tax_rate2;
|
||||||
$invoice->tax_name3 = $recurring_invoice->tax_name3;
|
$invoice->tax_name3 = $recurring_invoice->tax_name3;
|
||||||
$invoice->tax_rate3 = $recurring_invoice->tax_rate3;
|
$invoice->tax_rate3 = $recurring_invoice->tax_rate3;
|
||||||
|
$invoice->total_taxes = $recurring_invoice->total_taxes;
|
||||||
|
$invoice->subscription_id = $recurring_invoice->subscription_id;
|
||||||
$invoice->custom_value1 = $recurring_invoice->custom_value1;
|
$invoice->custom_value1 = $recurring_invoice->custom_value1;
|
||||||
$invoice->custom_value2 = $recurring_invoice->custom_value2;
|
$invoice->custom_value2 = $recurring_invoice->custom_value2;
|
||||||
$invoice->custom_value3 = $recurring_invoice->custom_value3;
|
$invoice->custom_value3 = $recurring_invoice->custom_value3;
|
||||||
|
@ -21,7 +21,7 @@ class TaskStatusFactory
|
|||||||
$task_status->user_id = $user_id;
|
$task_status->user_id = $user_id;
|
||||||
$task_status->company_id = $company_id;
|
$task_status->company_id = $company_id;
|
||||||
$task_status->name = '';
|
$task_status->name = '';
|
||||||
$task_status->color = '#fff';
|
$task_status->color = '';
|
||||||
$task_status->status_order = 9999;
|
$task_status->status_order = 9999;
|
||||||
|
|
||||||
return $task_status;
|
return $task_status;
|
||||||
|
@ -510,7 +510,7 @@ class ClientController extends BaseController
|
|||||||
$action = request()->input('action');
|
$action = request()->input('action');
|
||||||
|
|
||||||
$ids = request()->input('ids');
|
$ids = request()->input('ids');
|
||||||
$clients = Client::withTrashed()->find($this->transformKeys($ids));
|
$clients = Client::withTrashed()->whereIn('id', $this->transformKeys($ids))->cursor();
|
||||||
|
|
||||||
$clients->each(function ($client, $key) use ($action) {
|
$clients->each(function ($client, $key) use ($action) {
|
||||||
if (auth()->user()->can('edit', $client)) {
|
if (auth()->user()->can('edit', $client)) {
|
||||||
|
@ -243,6 +243,10 @@ class PaymentController extends Controller
|
|||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!$is_credit_payment){
|
||||||
|
$credit_totals = 0;
|
||||||
|
}
|
||||||
|
|
||||||
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals))];
|
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals))];
|
||||||
|
|
||||||
if ($request->query('hash')) {
|
if ($request->query('hash')) {
|
||||||
@ -257,11 +261,19 @@ class PaymentController extends Controller
|
|||||||
|
|
||||||
$payment_hash->save();
|
$payment_hash->save();
|
||||||
|
|
||||||
|
if($is_credit_payment){
|
||||||
|
$amount_with_fee = max(0, (($invoice_totals + $fee_totals) - $credit_totals));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$credit_totals = 0;
|
||||||
|
$amount_with_fee = max(0, $invoice_totals + $fee_totals);
|
||||||
|
}
|
||||||
|
|
||||||
$totals = [
|
$totals = [
|
||||||
'credit_totals' => $credit_totals,
|
'credit_totals' => $credit_totals,
|
||||||
'invoice_totals' => $invoice_totals,
|
'invoice_totals' => $invoice_totals,
|
||||||
'fee_total' => $fee_totals,
|
'fee_total' => $fee_totals,
|
||||||
'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals)),
|
'amount_with_fee' => $amount_with_fee,
|
||||||
];
|
];
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
@ -273,7 +285,7 @@ class PaymentController extends Controller
|
|||||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($is_credit_payment) {
|
if ($is_credit_payment || $totals <= 0) {
|
||||||
return $this->processCreditPayment($request, $data);
|
return $this->processCreditPayment($request, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,9 @@ class QuoteController extends Controller
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
if (! $quotes || $quotes->count() == 0) {
|
if (! $quotes || $quotes->count() == 0) {
|
||||||
return;
|
return redirect()
|
||||||
|
->route('client.quotes.index')
|
||||||
|
->with('message', ctrans('texts.no_quotes_available_for_download'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($quotes->count() == 1) {
|
if ($quotes->count() == 1) {
|
||||||
@ -121,7 +123,9 @@ class QuoteController extends Controller
|
|||||||
->get();
|
->get();
|
||||||
|
|
||||||
if (!$quotes || $quotes->count() == 0) {
|
if (!$quotes || $quotes->count() == 0) {
|
||||||
return redirect()->route('client.quotes.index');
|
return redirect()
|
||||||
|
->route('client.quotes.index')
|
||||||
|
->with('message', ctrans('texts.quotes_with_status_sent_can_be_approved'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($process) {
|
if ($process) {
|
||||||
|
@ -44,7 +44,6 @@ class SubscriptionPlanSwitchController extends Controller
|
|||||||
if(is_null($amount))
|
if(is_null($amount))
|
||||||
render('subscriptions.denied');
|
render('subscriptions.denied');
|
||||||
|
|
||||||
|
|
||||||
return render('subscriptions.switch', [
|
return render('subscriptions.switch', [
|
||||||
'subscription' => $recurring_invoice->subscription,
|
'subscription' => $recurring_invoice->subscription,
|
||||||
'recurring_invoice' => $recurring_invoice,
|
'recurring_invoice' => $recurring_invoice,
|
||||||
|
@ -474,6 +474,10 @@ class CompanyController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function destroy(DestroyCompanyRequest $request, Company $company)
|
public function destroy(DestroyCompanyRequest $request, Company $company)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id)
|
||||||
|
return response()->json(['message' => 'Cannot purge this company'], 400);
|
||||||
|
|
||||||
$company_count = $company->account->companies->count();
|
$company_count = $company->account->companies->count();
|
||||||
$account = $company->account;
|
$account = $company->account;
|
||||||
$account_key = $account->key;
|
$account_key = $account->key;
|
||||||
@ -577,4 +581,9 @@ class CompanyController extends BaseController
|
|||||||
return $this->itemResponse($company->fresh());
|
return $this->itemResponse($company->fresh());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// public function default(DefaultCompanyRequest $request, Company $company)
|
||||||
|
// {
|
||||||
|
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
52
app/Http/Controllers/HostedMigrationController.php
Normal file
52
app/Http/Controllers/HostedMigrationController.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Jobs\Account\CreateAccount;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\CompanyToken;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class HostedMigrationController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function getAccount(Request $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
if($request->header('X-API-HOSTED-SECRET') != config('ninja.ninja_hosted_secret'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if($user = MultiDB::hasUser(['email' => $request->input('email')]))
|
||||||
|
{
|
||||||
|
|
||||||
|
if($user->account->owner() && $user->account->companies()->count() >= 1)
|
||||||
|
{
|
||||||
|
return response()->json(['token' => $user->account->companies->first()->tokens->first()->token] ,200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['error' => 'This user is not able to perform a migration. Please contact us at contact@invoiceninja.com to discuss.'], 401);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$account = CreateAccount::dispatchNow($request->all(), $request->getClientIp());
|
||||||
|
|
||||||
|
$company = $account->companies->first();
|
||||||
|
|
||||||
|
$company_token = CompanyToken::where('user_id', auth()->user()->id)
|
||||||
|
->where('company_id', $company->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return response()->json(['token' => $company_token->token], 200);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,6 +16,7 @@ use App\Utils\CurlUtils;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class LicenseController extends BaseController
|
class LicenseController extends BaseController
|
||||||
{
|
{
|
||||||
@ -152,7 +153,7 @@ class LicenseController extends BaseController
|
|||||||
{
|
{
|
||||||
$account = auth()->user()->company()->account;
|
$account = auth()->user()->company()->account;
|
||||||
|
|
||||||
if($account->plan == 'white_label' && $account->plan_expires->lt(now())){
|
if($account->plan == 'white_label' && Carbon::parse($account->plan_expires)->lt(now())){
|
||||||
$account->plan = null;
|
$account->plan = null;
|
||||||
$account->plan_expires = null;
|
$account->plan_expires = null;
|
||||||
$account->save();
|
$account->save();
|
||||||
|
@ -82,6 +82,9 @@ class MigrationController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function purgeCompany(Company $company)
|
public function purgeCompany(Company $company)
|
||||||
{
|
{
|
||||||
|
if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id)
|
||||||
|
return response()->json(['message' => 'Cannot purge this company'], 400);
|
||||||
|
|
||||||
$account = $company->account;
|
$account = $company->account;
|
||||||
$company_id = $company->id;
|
$company_id = $company->id;
|
||||||
|
|
||||||
@ -102,6 +105,9 @@ class MigrationController extends BaseController
|
|||||||
|
|
||||||
private function purgeCompanyWithForceFlag(Company $company)
|
private function purgeCompanyWithForceFlag(Company $company)
|
||||||
{
|
{
|
||||||
|
if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id)
|
||||||
|
return response()->json(['message' => 'Cannot purge this company'], 400);
|
||||||
|
|
||||||
$account = $company->account;
|
$account = $company->account;
|
||||||
$company_id = $company->id;
|
$company_id = $company->id;
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\Payments\PaymentNotificationWebhookRequest;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Auth;
|
||||||
|
|
||||||
|
class PaymentNotificationWebhookController extends Controller
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public function __invoke(PaymentNotificationWebhookRequest $request, string $company_key, string $company_gateway_id, string $client_hash)
|
||||||
|
{
|
||||||
|
|
||||||
|
$company_gateway = CompanyGateway::find($this->decodePrimaryKey($company_gateway_id));
|
||||||
|
$client = Client::find($this->decodePrimaryKey($client_hash));
|
||||||
|
|
||||||
|
return $company_gateway
|
||||||
|
->driver($client)
|
||||||
|
->processWebhookRequest($request);
|
||||||
|
}
|
||||||
|
}
|
@ -470,7 +470,7 @@ class ProductController extends BaseController
|
|||||||
|
|
||||||
$ids = request()->input('ids');
|
$ids = request()->input('ids');
|
||||||
|
|
||||||
$products = Product::withTrashed()->find($this->transformKeys($ids));
|
$products = Product::withTrashed()->whereIn('id', $this->transformKeys($ids))->cursor();
|
||||||
|
|
||||||
$products->each(function ($product, $key) use ($action) {
|
$products->each(function ($product, $key) use ($action) {
|
||||||
if (auth()->user()->can('edit', $product)) {
|
if (auth()->user()->can('edit', $product)) {
|
||||||
|
@ -51,7 +51,7 @@ class StripeConnectController extends BaseController
|
|||||||
|
|
||||||
$config = $company_gateway->getConfig();
|
$config = $company_gateway->getConfig();
|
||||||
|
|
||||||
if(property_exists($config, 'account_id'))
|
if(property_exists($config, 'account_id') && strlen($config->account_id) > 1)
|
||||||
return view('auth.connect.existing');
|
return view('auth.connect.existing');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ class General extends Component
|
|||||||
'first_name' => ['sometimes'],
|
'first_name' => ['sometimes'],
|
||||||
'last_name' => ['sometimes'],
|
'last_name' => ['sometimes'],
|
||||||
'email' => ['required', 'email'],
|
'email' => ['required', 'email'],
|
||||||
|
'phone' => ['sometimes'],
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
|
@ -58,9 +58,6 @@ class PasswordProtection
|
|||||||
$google = new Google();
|
$google = new Google();
|
||||||
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
|
$user = $google->getTokenResponse(request()->header('X-API-OAUTH-PASSWORD'));
|
||||||
|
|
||||||
nlog("user");
|
|
||||||
nlog($user);
|
|
||||||
|
|
||||||
if (is_array($user)) {
|
if (is_array($user)) {
|
||||||
|
|
||||||
$query = [
|
$query = [
|
||||||
@ -68,8 +65,6 @@ class PasswordProtection
|
|||||||
'oauth_provider_id'=> 'google'
|
'oauth_provider_id'=> 'google'
|
||||||
];
|
];
|
||||||
|
|
||||||
nlog($query);
|
|
||||||
|
|
||||||
//If OAuth and user also has a password set - check both
|
//If OAuth and user also has a password set - check both
|
||||||
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
|
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ class QueryLogging
|
|||||||
$timeEnd = microtime(true);
|
$timeEnd = microtime(true);
|
||||||
$time = $timeEnd - $timeStart;
|
$time = $timeEnd - $timeStart;
|
||||||
|
|
||||||
if($count > 150)
|
// if($count > 150)
|
||||||
nlog($queries);
|
// nlog($queries);
|
||||||
|
|
||||||
$ip = '';
|
$ip = '';
|
||||||
|
|
||||||
|
@ -30,17 +30,22 @@ class UrlSetDb
|
|||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (config('ninja.db.multi_db_enabled')) {
|
if (config('ninja.db.multi_db_enabled')) {
|
||||||
$hashids = new Hashids('', 10); //decoded output is _always_ an array.
|
$hashids = new Hashids(config('ninja.hash_salt'), 10);
|
||||||
|
|
||||||
//parse URL hash and set DB
|
//parse URL hash and set DB
|
||||||
$segments = explode('-', $request->route('confirmation_code'));
|
$segments = explode('-', $request->route('confirmation_code'));
|
||||||
|
|
||||||
|
if(!is_array($segments))
|
||||||
|
return response()->json(['message' => 'Invalid confirmation code'], 403);
|
||||||
|
|
||||||
$hashed_db = $hashids->decode($segments[0]);
|
$hashed_db = $hashids->decode($segments[0]);
|
||||||
|
|
||||||
MultiDB::setDB(MultiDB::DB_PREFIX.str_pad($hashed_db[0], 2, '0', STR_PAD_LEFT));
|
MultiDB::setDB(MultiDB::DB_PREFIX.str_pad($hashed_db[0], 2, '0', STR_PAD_LEFT));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,8 @@ class CreateAccountRequest extends Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function prepareForValidation()
|
protected function prepareForValidation()
|
||||||
{nlog($this->all());
|
{
|
||||||
|
|
||||||
$input = $this->all();
|
$input = $this->all();
|
||||||
|
|
||||||
$input['user_agent'] = request()->server('HTTP_USER_AGENT');
|
$input['user_agent'] = request()->server('HTTP_USER_AGENT');
|
||||||
|
@ -47,27 +47,32 @@ class StoreCompanyGatewayRequest extends Request
|
|||||||
|
|
||||||
$gateway = Gateway::where('key', $input['gateway_key'])->first();
|
$gateway = Gateway::where('key', $input['gateway_key'])->first();
|
||||||
|
|
||||||
$default_gateway_fields = json_decode($gateway->fields);
|
if($gateway);
|
||||||
|
{
|
||||||
|
|
||||||
/*Force gateway properties */
|
$default_gateway_fields = json_decode($gateway->fields);
|
||||||
if (isset($input['config']) && is_object(json_decode($input['config']))) {
|
|
||||||
|
|
||||||
foreach (json_decode($input['config']) as $key => $value) {
|
/*Force gateway properties */
|
||||||
|
if (isset($input['config']) && is_object(json_decode($input['config']))) {
|
||||||
|
|
||||||
$default_gateway_fields->{$key} = $value;
|
foreach (json_decode($input['config']) as $key => $value) {
|
||||||
|
|
||||||
|
$default_gateway_fields->{$key} = $value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$input['config'] = json_encode($default_gateway_fields);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$input['config'] = json_encode($default_gateway_fields);
|
if (isset($input['config']))
|
||||||
|
$input['config'] = encrypt($input['config']);
|
||||||
|
|
||||||
|
if (isset($input['fees_and_limits']))
|
||||||
|
$input['fees_and_limits'] = $this->cleanFeesAndLimits($input['fees_and_limits']);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($input['config']))
|
|
||||||
$input['config'] = encrypt($input['config']);
|
|
||||||
|
|
||||||
if (isset($input['fees_and_limits']))
|
|
||||||
$input['fees_and_limits'] = $this->cleanFeesAndLimits($input['fees_and_limits']);
|
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class StoreExpenseRequest extends Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||||
$input['color'] = '#fff';
|
$input['color'] = '';
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ class StoreExpenseCategoryRequest extends Request
|
|||||||
$input = $this->decodePrimaryKeys($input);
|
$input = $this->decodePrimaryKeys($input);
|
||||||
|
|
||||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||||
$input['color'] = '#fff';
|
$input['color'] = '';
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class UpdateExpenseCategoryRequest extends Request
|
|||||||
$input = $this->all();
|
$input = $this->all();
|
||||||
|
|
||||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||||
$input['color'] = '#fff';
|
$input['color'] = '';
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Payments;
|
||||||
|
|
||||||
|
use App\Http\Requests\Request;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\CompanyGateway;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\PaymentHash;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
|
||||||
|
class PaymentNotificationWebhookRequest extends Request
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -51,7 +51,7 @@ class StoreProjectRequest extends Request
|
|||||||
|
|
||||||
|
|
||||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||||
$input['color'] = '#fff';
|
$input['color'] = '';
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ class UpdateProjectRequest extends Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||||
$input['color'] = '#fff';
|
$input['color'] = '';
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ class UpdateTaskRequest extends Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||||
$input['color'] = '#fff';
|
$input['color'] = '';
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ class StoreTaskStatusRequest extends Request
|
|||||||
$input = $this->all();
|
$input = $this->all();
|
||||||
|
|
||||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||||
$input['color'] = '#fff';
|
$input['color'] = '';
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ class UpdateTaskStatusRequest extends Request
|
|||||||
$input = $this->all();
|
$input = $this->all();
|
||||||
|
|
||||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||||
$input['color'] = '#fff';
|
$input['color'] = '';
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -45,14 +45,15 @@ class ClientMap
|
|||||||
26 => 'client.vat_number',
|
26 => 'client.vat_number',
|
||||||
27 => 'client.id_number',
|
27 => 'client.id_number',
|
||||||
28 => 'client.public_notes',
|
28 => 'client.public_notes',
|
||||||
29 => 'contact.first_name',
|
29 => 'client.phone',
|
||||||
30 => 'contact.last_name',
|
30 => 'contact.first_name',
|
||||||
31 => 'contact.email',
|
31 => 'contact.last_name',
|
||||||
32 => 'contact.phone',
|
32 => 'contact.email',
|
||||||
33 => 'contact.custom_value1',
|
33 => 'contact.phone',
|
||||||
34 => 'contact.custom_value2',
|
34 => 'contact.custom_value1',
|
||||||
35 => 'contact.custom_value3',
|
35 => 'contact.custom_value2',
|
||||||
36 => 'contact.custom_value4',
|
36 => 'contact.custom_value3',
|
||||||
|
37 => 'contact.custom_value4',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,14 +89,15 @@ class ClientMap
|
|||||||
26 => 'texts.vat_number',
|
26 => 'texts.vat_number',
|
||||||
27 => 'texts.id_number',
|
27 => 'texts.id_number',
|
||||||
28 => 'texts.public_notes',
|
28 => 'texts.public_notes',
|
||||||
29 => 'texts.first_name',
|
29 => 'texts.client_phone',
|
||||||
30 => 'texts.last_name',
|
30 => 'texts.first_name',
|
||||||
31 => 'texts.email',
|
31 => 'texts.last_name',
|
||||||
32 => 'texts.phone',
|
32 => 'texts.email',
|
||||||
33 => 'texts.custom_value',
|
33 => 'texts.phone',
|
||||||
34 => 'texts.custom_value',
|
34 => 'texts.custom_value',
|
||||||
35 => 'texts.custom_value',
|
35 => 'texts.custom_value',
|
||||||
36 => 'texts.custom_value',
|
36 => 'texts.custom_value',
|
||||||
|
37 => 'texts.custom_value',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class ExpenseTransformer extends BaseTransformer {
|
|||||||
'currency_id' => $this->getCurrencyByCode( $data, 'expense.currency_id' ),
|
'currency_id' => $this->getCurrencyByCode( $data, 'expense.currency_id' ),
|
||||||
'vendor_id' => isset( $data['expense.vendor'] ) ? $this->getVendorId( $data['expense.vendor'] ) : null,
|
'vendor_id' => isset( $data['expense.vendor'] ) ? $this->getVendorId( $data['expense.vendor'] ) : null,
|
||||||
'client_id' => isset( $data['expense.client'] ) ? $this->getClientId( $data['expense.client'] ) : null,
|
'client_id' => isset( $data['expense.client'] ) ? $this->getClientId( $data['expense.client'] ) : null,
|
||||||
'expense_date' => isset( $data['expense.date'] ) ? date( 'Y-m-d', strtotime( $data['expense.date'] ) ) : null,
|
'date' => isset( $data['expense.date'] ) ? date( 'Y-m-d', strtotime( $data['expense.date'] ) ) : null,
|
||||||
'public_notes' => $this->getString( $data, 'expense.public_notes' ),
|
'public_notes' => $this->getString( $data, 'expense.public_notes' ),
|
||||||
'private_notes' => $this->getString( $data, 'expense.private_notes' ),
|
'private_notes' => $this->getString( $data, 'expense.private_notes' ),
|
||||||
'expense_category_id' => isset( $data['expense.category'] ) ? $this->getExpenseCategoryId( $data['expense.category'] ) : null,
|
'expense_category_id' => isset( $data['expense.category'] ) ? $this->getExpenseCategoryId( $data['expense.category'] ) : null,
|
||||||
|
@ -77,7 +77,13 @@ class CreateAccount
|
|||||||
$sp794f3f->key = Str::random(32);
|
$sp794f3f->key = Str::random(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sp794f3f->save();
|
if(Ninja::isHosted())
|
||||||
|
{
|
||||||
|
$sp794f3f->trial_started = now();
|
||||||
|
$sp794f3f->trial_plan = 'pro';
|
||||||
|
// $sp794f3f->plan = 'pro';
|
||||||
|
$sp794f3f->save();
|
||||||
|
}
|
||||||
|
|
||||||
$sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f);
|
$sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f);
|
||||||
$sp035a66->load('account');
|
$sp035a66->load('account');
|
||||||
|
@ -48,6 +48,9 @@ class CreateCompanyTaskStatuses
|
|||||||
|
|
||||||
MultiDB::setDb($this->company->db);
|
MultiDB::setDb($this->company->db);
|
||||||
|
|
||||||
|
if(TaskStatus::where('company_id', $this->company->id)->count() > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
$task_statuses = [
|
$task_statuses = [
|
||||||
['name' => ctrans('texts.backlog'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 1],
|
['name' => ctrans('texts.backlog'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 1],
|
||||||
['name' => ctrans('texts.ready_to_do'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 2],
|
['name' => ctrans('texts.ready_to_do'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 2],
|
||||||
|
@ -131,8 +131,8 @@ class CreateEntityPdf implements ShouldQueue
|
|||||||
|
|
||||||
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->entity->client->getSetting($entity_design_id));
|
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->entity->client->getSetting($entity_design_id));
|
||||||
|
|
||||||
if(!$this->company->account->hasFeature(Account::FEATURE_DIFFERENT_DESIGNS))
|
// if(!$this->company->account->hasFeature(Account::FEATURE_DIFFERENT_DESIGNS))
|
||||||
$entity_design_id = 2;
|
// $entity_design_id = 2;
|
||||||
|
|
||||||
$design = Design::find($entity_design_id);
|
$design = Design::find($entity_design_id);
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class CreateUser
|
|||||||
{
|
{
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->account_id = $this->account->id;
|
$user->account_id = $this->account->id;
|
||||||
$user->password = bcrypt($this->request['password']);
|
$user->password = $this->request['password'] ? bcrypt($this->request['password']) : '';
|
||||||
$user->accepted_terms_version = config('ninja.terms_version');
|
$user->accepted_terms_version = config('ninja.terms_version');
|
||||||
$user->confirmation_code = $this->createDbHash(config('database.default'));
|
$user->confirmation_code = $this->createDbHash(config('database.default'));
|
||||||
$user->fill($this->request);
|
$user->fill($this->request);
|
||||||
|
@ -192,6 +192,8 @@ class Import implements ShouldQueue
|
|||||||
|
|
||||||
nlog("Starting Migration");
|
nlog("Starting Migration");
|
||||||
nlog($this->user->email);
|
nlog($this->user->email);
|
||||||
|
info("Starting Migration");
|
||||||
|
info($this->user->email);
|
||||||
|
|
||||||
auth()->login($this->user, false);
|
auth()->login($this->user, false);
|
||||||
auth()->user()->setCompany($this->company);
|
auth()->user()->setCompany($this->company);
|
||||||
@ -317,6 +319,12 @@ class Import implements ShouldQueue
|
|||||||
$account = $this->company->account;
|
$account = $this->company->account;
|
||||||
$account->fill($data);
|
$account->fill($data);
|
||||||
$account->save();
|
$account->save();
|
||||||
|
|
||||||
|
//Prevent hosted users being pushed into a trial
|
||||||
|
if(Ninja::isHosted() && $account->plan != ''){
|
||||||
|
$account->trial_plan = '';
|
||||||
|
$account->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -422,6 +430,7 @@ class Import implements ShouldQueue
|
|||||||
|
|
||||||
private function transformCompanyData(array $data): array
|
private function transformCompanyData(array $data): array
|
||||||
{
|
{
|
||||||
|
|
||||||
$company_settings = CompanySettings::defaults();
|
$company_settings = CompanySettings::defaults();
|
||||||
|
|
||||||
if (array_key_exists('settings', $data)) {
|
if (array_key_exists('settings', $data)) {
|
||||||
@ -444,6 +453,7 @@ class Import implements ShouldQueue
|
|||||||
$data['settings'] = $company_settings;
|
$data['settings'] = $company_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,7 +534,7 @@ class Import implements ShouldQueue
|
|||||||
|
|
||||||
$user = $user_repository->save($modified, $this->fetchUser($resource['email']), true, true);
|
$user = $user_repository->save($modified, $this->fetchUser($resource['email']), true, true);
|
||||||
$user->email_verified_at = now();
|
$user->email_verified_at = now();
|
||||||
$user->confirmation_code = '';
|
// $user->confirmation_code = '';
|
||||||
|
|
||||||
if($modified['deleted_at'])
|
if($modified['deleted_at'])
|
||||||
$user->deleted_at = now();
|
$user->deleted_at = now();
|
||||||
@ -556,13 +566,13 @@ class Import implements ShouldQueue
|
|||||||
{
|
{
|
||||||
$value = trim($value);
|
$value = trim($value);
|
||||||
|
|
||||||
$model_query = (new $model())
|
$model_query = $model::where($column, $value)
|
||||||
->query()
|
->where('company_id', $this->company->id)
|
||||||
->where($column, $value)
|
->withTrashed()
|
||||||
->exists();
|
->exists();
|
||||||
|
|
||||||
if($model_query)
|
if($model_query)
|
||||||
return $value.'_'. Str::random(5);
|
return $value . '_' . Str::random(5);
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
@ -1099,10 +1109,8 @@ class Import implements ShouldQueue
|
|||||||
|
|
||||||
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
|
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
|
||||||
$modified['user_id'] = $this->processUserId($resource);
|
$modified['user_id'] = $this->processUserId($resource);
|
||||||
//$modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
|
|
||||||
$modified['company_id'] = $this->company->id;
|
$modified['company_id'] = $this->company->id;
|
||||||
|
|
||||||
//unset($modified['invoices']);
|
|
||||||
unset($modified['invoice_id']);
|
unset($modified['invoice_id']);
|
||||||
|
|
||||||
if (isset($modified['invoices'])) {
|
if (isset($modified['invoices'])) {
|
||||||
@ -1111,8 +1119,8 @@ class Import implements ShouldQueue
|
|||||||
$modified['invoices'][$key]['invoice_id'] = $this->transformId('invoices', $invoice['invoice_id']);
|
$modified['invoices'][$key]['invoice_id'] = $this->transformId('invoices', $invoice['invoice_id']);
|
||||||
} else {
|
} else {
|
||||||
nlog($modified['invoices']);
|
nlog($modified['invoices']);
|
||||||
// $modified['credits'][$key]['credit_id'] = $this->transformId('credits', $invoice['invoice_id']);
|
unset($modified['invoices']);
|
||||||
// $modified['credits'][$key]['amount'] = $modified['invoices'][$key]['amount'];
|
//if the transformation didn't work - you _must_ unset this data as it will be incorrect!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1239,6 +1247,7 @@ class Import implements ShouldQueue
|
|||||||
|
|
||||||
$try_quote = false;
|
$try_quote = false;
|
||||||
$exception = false;
|
$exception = false;
|
||||||
|
$entity = false;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$invoice_id = $this->transformId('invoices', $resource['invoice_id']);
|
$invoice_id = $this->transformId('invoices', $resource['invoice_id']);
|
||||||
|
76
app/Jobs/Util/RefreshPdfs.php
Normal file
76
app/Jobs/Util/RefreshPdfs.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Jobs\Util;
|
||||||
|
|
||||||
|
use App\Jobs\Entity\CreateEntityPdf;
|
||||||
|
use App\Jobs\Util\UnlinkFile;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\Account;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\CreditInvitation;
|
||||||
|
use App\Models\InvoiceInvitation;
|
||||||
|
use App\Models\QuoteInvitation;
|
||||||
|
use App\Utils\Ninja;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class RefreshPdfs implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $company;
|
||||||
|
|
||||||
|
public function __construct(Company $company)
|
||||||
|
{
|
||||||
|
$this->company = $company;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
|
||||||
|
MultiDB::setDb($this->company->db);
|
||||||
|
|
||||||
|
|
||||||
|
InvoiceInvitation::where('company_id', $this->company->id)->cursor()->each(function ($invitation) {
|
||||||
|
|
||||||
|
nlog("generating invoice pdf for {$invitation->invoice_id}");
|
||||||
|
CreateEntityPdf::dispatch($invitation);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
QuoteInvitation::where('company_id', $this->company->id)->cursor()->each(function ($invitation) {
|
||||||
|
|
||||||
|
nlog("generating quote pdf for {$invitation->quote_id}");
|
||||||
|
CreateEntityPdf::dispatch($invitation);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
CreditInvitation::where('company_id', $this->company->id)->cursor()->each(function ($invitation) {
|
||||||
|
|
||||||
|
nlog("generating credit pdf for {$invitation->credit_id}");
|
||||||
|
CreateEntityPdf::dispatch($invitation);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class VersionCheck implements ShouldQueue
|
class VersionCheck implements ShouldQueue
|
||||||
{
|
{
|
||||||
@ -49,7 +50,7 @@ class VersionCheck implements ShouldQueue
|
|||||||
if(!$account)
|
if(!$account)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if($account->plan == 'white_label' && $account->plan_expires && $account->plan_expires->lt(now())){
|
if($account->plan == 'white_label' && $account->plan_expires && Carbon::parse($account->plan_expires)->lt(now())){
|
||||||
$account->plan = null;
|
$account->plan = null;
|
||||||
$account->plan_expires = null;
|
$account->plan_expires = null;
|
||||||
$account->save();
|
$account->save();
|
||||||
|
@ -59,7 +59,7 @@ class MultiDB
|
|||||||
$current_db = config('database.default');
|
$current_db = config('database.default');
|
||||||
|
|
||||||
foreach (self::$dbs as $db) {
|
foreach (self::$dbs as $db) {
|
||||||
if (Company::on($db)->whereSubdomain($subdomain)->get()->count() >= 1) {
|
if (Company::on($db)->whereSubdomain($subdomain)->exists()) {
|
||||||
self::setDb($current_db);
|
self::setDb($current_db);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -73,12 +73,12 @@ class MultiDB
|
|||||||
public static function checkUserEmailExists($email) : bool
|
public static function checkUserEmailExists($email) : bool
|
||||||
{
|
{
|
||||||
if (! config('ninja.db.multi_db_enabled'))
|
if (! config('ninja.db.multi_db_enabled'))
|
||||||
return User::where(['email' => $email])->get()->count() >= 1 ?? false; // true >= 1 emails found / false -> == emails found
|
return User::where(['email' => $email])->exists(); // true >= 1 emails found / false -> == emails found
|
||||||
|
|
||||||
$current_db = config('database.default');
|
$current_db = config('database.default');
|
||||||
|
|
||||||
foreach (self::$dbs as $db) {
|
foreach (self::$dbs as $db) {
|
||||||
if (User::on($db)->where(['email' => $email])->get()->count() >= 1) { // if user already exists, validation will fail
|
if (User::on($db)->where(['email' => $email])->exists()) { // if user already exists, validation will fail
|
||||||
self::setDb($current_db);
|
self::setDb($current_db);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -196,7 +196,7 @@ class MultiDB
|
|||||||
//multi-db active
|
//multi-db active
|
||||||
foreach (self::$dbs as $db) {
|
foreach (self::$dbs as $db) {
|
||||||
|
|
||||||
if (User::on($db)->where('email', $email)->count() >= 1){
|
if (User::on($db)->where('email', $email)->exists()){
|
||||||
self::setDb($db);
|
self::setDb($db);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -214,7 +214,7 @@ class MultiDB
|
|||||||
//multi-db active
|
//multi-db active
|
||||||
foreach (self::$dbs as $db) {
|
foreach (self::$dbs as $db) {
|
||||||
|
|
||||||
if (Document::on($db)->where('hash', $hash)->count() >= 1){
|
if (Document::on($db)->where('hash', $hash)->exists()){
|
||||||
self::setDb($db);
|
self::setDb($db);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ class PaymentUpdatedActivity implements ShouldQueue
|
|||||||
|
|
||||||
$fields = new stdClass;
|
$fields = new stdClass;
|
||||||
|
|
||||||
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->payment->user_id;
|
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->payment->user_id;
|
||||||
|
|
||||||
$fields->payment_id = $payment->id;
|
$fields->payment_id = $payment->id;
|
||||||
$fields->client_id = $payment->client_id;
|
$fields->client_id = $payment->client_id;
|
||||||
|
@ -39,6 +39,7 @@ class InvoicePaidActivity implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle($event)
|
public function handle($event)
|
||||||
{
|
{
|
||||||
|
|
||||||
MultiDB::setDb($event->company->db);
|
MultiDB::setDb($event->company->db);
|
||||||
|
|
||||||
$fields = new stdClass;
|
$fields = new stdClass;
|
||||||
@ -51,6 +52,12 @@ class InvoicePaidActivity implements ShouldQueue
|
|||||||
|
|
||||||
$this->activity_repo->save($fields, $event->invoice, $event->event_vars);
|
$this->activity_repo->save($fields, $event->invoice, $event->event_vars);
|
||||||
|
|
||||||
|
if($event->invoice->subscription()->exists())
|
||||||
|
{
|
||||||
|
nlog("subscription exists");
|
||||||
|
$event->invoice->subscription->service()->planPaid($event->invoice);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$event->invoice->service()->touchPdf();
|
$event->invoice->service()->touchPdf();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@ -71,7 +71,6 @@ class PaymentNotification implements ShouldQueue
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*Google Analytics Track Revenue*/
|
/*Google Analytics Track Revenue*/
|
||||||
if (isset($payment->company->google_analytics_key)) {
|
if (isset($payment->company->google_analytics_key)) {
|
||||||
$this->trackRevenue($event);
|
$this->trackRevenue($event);
|
||||||
|
@ -91,7 +91,7 @@ class EntityPaidObject
|
|||||||
'texts.notification_payment_paid_subject',
|
'texts.notification_payment_paid_subject',
|
||||||
['client' => $this->payment->client->present()->name()]
|
['client' => $this->payment->client->present()->name()]
|
||||||
),
|
),
|
||||||
'message' => ctrans(
|
'content' => ctrans(
|
||||||
'texts.notification_payment_paid',
|
'texts.notification_payment_paid',
|
||||||
['amount' => $amount,
|
['amount' => $amount,
|
||||||
'client' => $this->payment->client->present()->name(),
|
'client' => $this->payment->client->present()->name(),
|
||||||
|
@ -52,17 +52,21 @@ class SupportMessageSent extends Mailable
|
|||||||
|
|
||||||
$account = auth()->user()->account;
|
$account = auth()->user()->account;
|
||||||
|
|
||||||
$plan = $account->plan ?: 'Forever Free';
|
$priority = '';
|
||||||
|
$plan = $account->plan ?: '';
|
||||||
|
|
||||||
|
if(strlen($plan) >1)
|
||||||
|
$priority = '[PRIORITY] ';
|
||||||
|
|
||||||
$company = auth()->user()->company();
|
$company = auth()->user()->company();
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
if(Ninja::isHosted())
|
if(Ninja::isHosted())
|
||||||
$subject = "Hosted {$user->present()->name} - [{$plan} - {$company->db}]";
|
$subject = "{$priority}Hosted-{$company->db} :: Customer Support - [{$plan}] ".date('M jS, g:ia');
|
||||||
else
|
else
|
||||||
$subject = "Self Host {$user->present()->name} - [{$plan} - {$company->db}]";
|
$subject = "{$priority}Self Hosted :: Customer Support - [{$plan}] ".date('M jS, g:ia');
|
||||||
|
|
||||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
return $this->from(config('mail.from.address'), $user->present()->name())
|
||||||
->replyTo($user->email, $user->present()->name())
|
->replyTo($user->email, $user->present()->name())
|
||||||
->subject($subject)
|
->subject($subject)
|
||||||
->view('email.support.message', [
|
->view('email.support.message', [
|
||||||
|
@ -54,8 +54,8 @@ class Account extends BaseModel
|
|||||||
'deleted_at',
|
'deleted_at',
|
||||||
'promo_expires',
|
'promo_expires',
|
||||||
'discount_expires',
|
'discount_expires',
|
||||||
'trial_started',
|
// 'trial_started',
|
||||||
'plan_expires'
|
// 'plan_expires'
|
||||||
];
|
];
|
||||||
|
|
||||||
const PLAN_FREE = 'free';
|
const PLAN_FREE = 'free';
|
||||||
@ -120,6 +120,11 @@ class Account extends BaseModel
|
|||||||
return $this->hasMany(CompanyUser::class);
|
return $this->hasMany(CompanyUser::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function owner()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CompanyUser::class)->where('is_owner', true)->first() ? $this->hasMany(CompanyUser::class)->where('is_owner', true)->first()->user : false;
|
||||||
|
}
|
||||||
|
|
||||||
public function getPlan()
|
public function getPlan()
|
||||||
{
|
{
|
||||||
return $this->plan ?: '';
|
return $this->plan ?: '';
|
||||||
@ -256,7 +261,7 @@ class Account extends BaseModel
|
|||||||
|
|
||||||
if ($trial_plan && $include_trial) {
|
if ($trial_plan && $include_trial) {
|
||||||
$trial_started = $this->trial_started;
|
$trial_started = $this->trial_started;
|
||||||
$trial_expires = $this->trial_started->addSeconds($this->trial_duration);
|
$trial_expires = Carbon::parse($this->trial_started)->addSeconds($this->trial_duration);
|
||||||
|
|
||||||
if($trial_expires->greaterThan(now())){
|
if($trial_expires->greaterThan(now())){
|
||||||
$trial_active = true;
|
$trial_active = true;
|
||||||
|
@ -16,6 +16,7 @@ use App\Models\Presenters\CompanyPresenter;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\Notification\NotificationService;
|
use App\Services\Notification\NotificationService;
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
|
use App\Utils\Traits\AppSetup;
|
||||||
use App\Utils\Traits\CompanySettingsSaver;
|
use App\Utils\Traits\CompanySettingsSaver;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
use App\Utils\Traits\ThrottlesEmail;
|
use App\Utils\Traits\ThrottlesEmail;
|
||||||
@ -31,6 +32,7 @@ class Company extends BaseModel
|
|||||||
use MakesHash;
|
use MakesHash;
|
||||||
use CompanySettingsSaver;
|
use CompanySettingsSaver;
|
||||||
use ThrottlesEmail;
|
use ThrottlesEmail;
|
||||||
|
use AppSetup;
|
||||||
|
|
||||||
const ENTITY_RECURRING_INVOICE = 'recurring_invoice';
|
const ENTITY_RECURRING_INVOICE = 'recurring_invoice';
|
||||||
const ENTITY_CREDIT = 'credit';
|
const ENTITY_CREDIT = 'credit';
|
||||||
@ -311,7 +313,17 @@ class Company extends BaseModel
|
|||||||
|
|
||||||
public function timezone()
|
public function timezone()
|
||||||
{
|
{
|
||||||
return Timezone::find($this->settings->timezone_id);
|
|
||||||
|
$timezones = Cache::get('timezones');
|
||||||
|
|
||||||
|
if(!$timezones)
|
||||||
|
$this->buildCache(true);
|
||||||
|
|
||||||
|
return $timezones->filter(function ($item) {
|
||||||
|
return $item->id == $this->settings->timezone_id;
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
// return Timezone::find($this->settings->timezone_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function designs()
|
public function designs()
|
||||||
@ -339,7 +351,18 @@ class Company extends BaseModel
|
|||||||
*/
|
*/
|
||||||
public function language()
|
public function language()
|
||||||
{
|
{
|
||||||
return Language::find($this->settings->language_id);
|
|
||||||
|
$languages = Cache::get('languages');
|
||||||
|
|
||||||
|
if(!$languages)
|
||||||
|
$this->buildCache(true);
|
||||||
|
|
||||||
|
return $languages->filter(function ($item) {
|
||||||
|
return $item->id == $this->settings->language_id;
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
|
||||||
|
// return Language::find($this->settings->language_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLocale()
|
public function getLocale()
|
||||||
|
@ -371,6 +371,11 @@ class CompanyGateway extends BaseModel
|
|||||||
return $fee;
|
return $fee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function webhookUrl()
|
||||||
|
{
|
||||||
|
return route('payment_webhook', ['company_key' => $this->company->company_key, 'company_gateway_id' => $this->hashed_id]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* we need to average out the gateway fees across all the invoices
|
* we need to average out the gateway fees across all the invoices
|
||||||
* so lets iterate.
|
* so lets iterate.
|
||||||
@ -412,4 +417,6 @@ class CompanyGateway extends BaseModel
|
|||||||
return $this
|
return $this
|
||||||
->where('id', $this->decodePrimaryKey($value))->firstOrFail();
|
->where('id', $this->decodePrimaryKey($value))->firstOrFail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,7 @@ class Invoice extends BaseModel
|
|||||||
'exchange_rate',
|
'exchange_rate',
|
||||||
'subscription_id',
|
'subscription_id',
|
||||||
'auto_bill_enabled',
|
'auto_bill_enabled',
|
||||||
|
'uses_inclusive_taxes',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
@ -165,7 +166,7 @@ class Invoice extends BaseModel
|
|||||||
|
|
||||||
public function recurring_invoice()
|
public function recurring_invoice()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(RecurringInvoice::class)->withTrashed();
|
return $this->belongsTo(RecurringInvoice::class, 'recurring_id', 'id')->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assigned_user()
|
public function assigned_user()
|
||||||
|
@ -29,6 +29,7 @@ class PaymentType extends StaticModel
|
|||||||
const NOVA = 11;
|
const NOVA = 11;
|
||||||
const CREDIT_CARD_OTHER = 12;
|
const CREDIT_CARD_OTHER = 12;
|
||||||
const PAYPAL = 13;
|
const PAYPAL = 13;
|
||||||
|
const CHECK = 15;
|
||||||
const CARTE_BLANCHE = 16;
|
const CARTE_BLANCHE = 16;
|
||||||
const UNIONPAY = 17;
|
const UNIONPAY = 17;
|
||||||
const JCB = 18;
|
const JCB = 18;
|
||||||
|
@ -21,6 +21,10 @@ class UserPresenter extends EntityPresenter
|
|||||||
*/
|
*/
|
||||||
public function name()
|
public function name()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if(!$this->entity)
|
||||||
|
return "No User Object Available";
|
||||||
|
|
||||||
$first_name = isset($this->entity->first_name) ? $this->entity->first_name : '';
|
$first_name = isset($this->entity->first_name) ? $this->entity->first_name : '';
|
||||||
$last_name = isset($this->entity->last_name) ? $this->entity->last_name : '';
|
$last_name = isset($this->entity->last_name) ? $this->entity->last_name : '';
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ class SystemLog extends Model
|
|||||||
const TYPE_CUSTOM = 306;
|
const TYPE_CUSTOM = 306;
|
||||||
const TYPE_BRAINTREE = 307;
|
const TYPE_BRAINTREE = 307;
|
||||||
const TYPE_WEPAY = 309;
|
const TYPE_WEPAY = 309;
|
||||||
|
const TYPE_PAYFAST = 310;
|
||||||
|
|
||||||
|
|
||||||
const TYPE_QUOTA_EXCEEDED = 400;
|
const TYPE_QUOTA_EXCEEDED = 400;
|
||||||
|
@ -548,6 +548,15 @@ class BaseDriver extends AbstractPaymentDriver
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function genericWebhookUrl()
|
||||||
|
{
|
||||||
|
return route('payment_notification_webhook', [
|
||||||
|
'company_key' => $this->client->company->company_key,
|
||||||
|
'company_gateway_id' => $this->encodePrimaryKey($this->company_gateway->id),
|
||||||
|
'client' => $this->encodePrimaryKey($this->client->id),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/* Performs an extra iterate on the gatewayTypes() array and passes back only the enabled gateways*/
|
/* Performs an extra iterate on the gatewayTypes() array and passes back only the enabled gateways*/
|
||||||
public function gatewayTypeEnabled($type)
|
public function gatewayTypeEnabled($type)
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace App\PaymentDrivers;
|
namespace App\PaymentDrivers;
|
||||||
|
|
||||||
|
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||||
use App\Models\ClientGatewayToken;
|
use App\Models\ClientGatewayToken;
|
||||||
use App\Models\GatewayType;
|
use App\Models\GatewayType;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
@ -39,6 +40,22 @@ class DriverTemplate extends BaseDriver
|
|||||||
|
|
||||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
|
const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
return $this; /* This is where you boot the gateway with your auth credentials*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns an array of gateway types for the payment gateway */
|
||||||
|
public function gatewayTypes(): array
|
||||||
|
{
|
||||||
|
$types = [];
|
||||||
|
|
||||||
|
$types[] = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets the payment method initialized */
|
||||||
public function setPaymentMethod($payment_method_id)
|
public function setPaymentMethod($payment_method_id)
|
||||||
{
|
{
|
||||||
$class = self::$methods[$payment_method_id];
|
$class = self::$methods[$payment_method_id];
|
||||||
@ -75,4 +92,8 @@ class DriverTemplate extends BaseDriver
|
|||||||
{
|
{
|
||||||
return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here
|
return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
282
app/PaymentDrivers/PayFast/CreditCard.php
Normal file
282
app/PaymentDrivers/PayFast/CreditCard.php
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\PaymentDrivers\PayFast;
|
||||||
|
|
||||||
|
use App\Exceptions\PaymentFailed;
|
||||||
|
use App\Jobs\Mail\PaymentFailureMailer;
|
||||||
|
use App\Jobs\Util\SystemLogger;
|
||||||
|
use App\Models\ClientGatewayToken;
|
||||||
|
use App\Models\GatewayType;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\PaymentHash;
|
||||||
|
use App\Models\PaymentType;
|
||||||
|
use App\Models\SystemLog;
|
||||||
|
use App\PaymentDrivers\PayFastPaymentDriver;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class CreditCard
|
||||||
|
{
|
||||||
|
|
||||||
|
public $payfast;
|
||||||
|
|
||||||
|
public function __construct(PayFastPaymentDriver $payfast)
|
||||||
|
{
|
||||||
|
$this->payfast = $payfast;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
$data = array();
|
||||||
|
$data['merchant_id'] = $this->getMerchantId();
|
||||||
|
$data['merchant_key'] = $this->getMerchantKey();
|
||||||
|
$data['return_url'] = $this->getReturnUrl();
|
||||||
|
$data['cancel_url'] = $this->getCancelUrl();
|
||||||
|
$data['notify_url'] = $this->getNotifyUrl();
|
||||||
|
|
||||||
|
if ($this->getCard()) {
|
||||||
|
$data['name_first'] = $this->getCard()->getFirstName();
|
||||||
|
$data['name_last'] = $this->getCard()->getLastName();
|
||||||
|
$data['email_address'] = $this->getCard()->getEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['m_payment_id'] = $this->getTransactionId();
|
||||||
|
$data['amount'] = $this->getAmount();
|
||||||
|
$data['item_name'] = $this->getDescription();
|
||||||
|
$data['custom_int1'] = $this->getCustomInt1();
|
||||||
|
$data['custom_int2'] = $this->getCustomInt2();
|
||||||
|
$data['custom_int3'] = $this->getCustomInt3();
|
||||||
|
$data['custom_int4'] = $this->getCustomInt4();
|
||||||
|
$data['custom_int5'] = $this->getCustomInt5();
|
||||||
|
$data['custom_str1'] = $this->getCustomStr1();
|
||||||
|
$data['custom_str2'] = $this->getCustomStr2();
|
||||||
|
$data['custom_str3'] = $this->getCustomStr3();
|
||||||
|
$data['custom_str4'] = $this->getCustomStr4();
|
||||||
|
$data['custom_str5'] = $this->getCustomStr5();
|
||||||
|
|
||||||
|
if ($this->getPaymentMethod()) {
|
||||||
|
$data['payment_method'] = $this->getPaymentMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 == $this->getSubscriptionType()) {
|
||||||
|
$data['subscription_type'] = $this->getSubscriptionType();
|
||||||
|
$data['billing_date'] = $this->getBillingDate();
|
||||||
|
$data['recurring_amount'] = $this->getRecurringAmount();
|
||||||
|
$data['frequency'] = $this->getFrequency();
|
||||||
|
$data['cycles'] = $this->getCycles();
|
||||||
|
}
|
||||||
|
if (2 == $this->getSubscriptionType()) {
|
||||||
|
$data['subscription_type'] = $this->getSubscriptionType();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['passphrase'] = $this->getParameter('passphrase'); 123456789012aV
|
||||||
|
$data['signature'] = $this->generateSignature($data);
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function authorizeView($data)
|
||||||
|
{
|
||||||
|
$hash = Str::random(32);
|
||||||
|
|
||||||
|
Cache::put($hash, 'cc_auth', 300);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'merchant_id' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||||
|
'merchant_key' => $this->payfast->company_gateway->getConfigField('merchantKey'),
|
||||||
|
'return_url' => route('client.payment_methods.index'),
|
||||||
|
'cancel_url' => route('client.payment_methods.index'),
|
||||||
|
'notify_url' => $this->payfast->genericWebhookUrl(),
|
||||||
|
'm_payment_id' => $hash,
|
||||||
|
'amount' => 5,
|
||||||
|
'item_name' => 'pre-auth',
|
||||||
|
'item_description' => 'Credit Card Pre Authorization',
|
||||||
|
'subscription_type' => 2,
|
||||||
|
'passphrase' => $this->payfast->company_gateway->getConfigField('passphrase'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$data['signature'] = $this->payfast->generateSignature($data);
|
||||||
|
$data['gateway'] = $this->payfast;
|
||||||
|
$data['payment_endpoint_url'] = $this->payfast->endpointUrl();
|
||||||
|
|
||||||
|
return render('gateways.payfast.authorize', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
'm_payment_id' => NULL,
|
||||||
|
'pf_payment_id' => '1409993',
|
||||||
|
'payment_status' => 'COMPLETE',
|
||||||
|
'item_name' => 'pre-auth',
|
||||||
|
'item_description' => NULL,
|
||||||
|
'amount_gross' => '5.00',
|
||||||
|
'amount_fee' => '-2.53',
|
||||||
|
'amount_net' => '2.47',
|
||||||
|
'custom_str1' => NULL,
|
||||||
|
'custom_str2' => NULL,
|
||||||
|
'custom_str3' => NULL,
|
||||||
|
'custom_str4' => NULL,
|
||||||
|
'custom_str5' => NULL,
|
||||||
|
'custom_int1' => NULL,
|
||||||
|
'custom_int2' => NULL,
|
||||||
|
'custom_int3' => NULL,
|
||||||
|
'custom_int4' => NULL,
|
||||||
|
'custom_int5' => NULL,
|
||||||
|
'name_first' => NULL,
|
||||||
|
'name_last' => NULL,
|
||||||
|
'email_address' => NULL,
|
||||||
|
'merchant_id' => '10023100',
|
||||||
|
'token' => '34b66bc2-3c54-9590-03ea-42ee8b89922a',
|
||||||
|
'billing_date' => '2021-07-05',
|
||||||
|
'signature' => 'ebdb4ca937d0e3f43462841c0afc6ad9',
|
||||||
|
'q' => '/payment_notification_webhook/EhbnVYyzJZyccY85hcHIkIzNPI2rtHzznAyyyG73oSxZidAdN9gf8BvAKDomqeHp/4openRe7Az/WPe99p3eLy',
|
||||||
|
*/
|
||||||
|
public function authorizeResponse($request)
|
||||||
|
{
|
||||||
|
$data = $request->all();
|
||||||
|
|
||||||
|
$cgt = [];
|
||||||
|
$cgt['token'] = $data['token'];
|
||||||
|
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
$payment_meta = new \stdClass;
|
||||||
|
$payment_meta->exp_month = 'xx';
|
||||||
|
$payment_meta->exp_year = 'xx';
|
||||||
|
$payment_meta->brand = 'CC';
|
||||||
|
$payment_meta->last4 = 'xxxx';
|
||||||
|
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
$cgt['payment_meta'] = $payment_meta;
|
||||||
|
|
||||||
|
$token = $this->payfast->storeGatewayToken($cgt, []);
|
||||||
|
|
||||||
|
return response()->json([], 200);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentView($data)
|
||||||
|
{
|
||||||
|
|
||||||
|
$payfast_data = [
|
||||||
|
'merchant_id' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||||
|
'merchant_key' => $this->payfast->company_gateway->getConfigField('merchantKey'),
|
||||||
|
'return_url' => route('client.payments.index'),
|
||||||
|
'cancel_url' => route('client.payment_methods.index'),
|
||||||
|
'notify_url' => $this->payfast->genericWebhookUrl(),
|
||||||
|
'm_payment_id' => $data['payment_hash'],
|
||||||
|
'amount' => $data['amount_with_fee'],
|
||||||
|
'item_name' => 'purchase',
|
||||||
|
'item_description' => ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number'),
|
||||||
|
'passphrase' => $this->payfast->company_gateway->getConfigField('passphrase'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$payfast_data['signature'] = $this->payfast->generateSignature($payfast_data);
|
||||||
|
$payfast_data['gateway'] = $this->payfast;
|
||||||
|
$payfast_data['payment_endpoint_url'] = $this->payfast->endpointUrl();
|
||||||
|
|
||||||
|
return render('gateways.payfast.pay', array_merge($data, $payfast_data));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
[2021-07-05 11:21:24] local.INFO: array (
|
||||||
|
'm_payment_id' => 'B7G9Q2vPhqkLEoMwwY1paXvPGuFxpbDe',
|
||||||
|
'pf_payment_id' => '1410364',
|
||||||
|
'payment_status' => 'COMPLETE',
|
||||||
|
'item_name' => 'purchase',
|
||||||
|
'item_description' => 'Invoices: ["0001"]',
|
||||||
|
'amount_gross' => '100.00',
|
||||||
|
'amount_fee' => '-2.30',
|
||||||
|
'amount_net' => '97.70',
|
||||||
|
'custom_str1' => NULL,
|
||||||
|
'custom_str2' => NULL,
|
||||||
|
'custom_str3' => NULL,
|
||||||
|
'custom_str4' => NULL,
|
||||||
|
'custom_str5' => NULL,
|
||||||
|
'custom_int1' => NULL,
|
||||||
|
'custom_int2' => NULL,
|
||||||
|
'custom_int3' => NULL,
|
||||||
|
'custom_int4' => NULL,
|
||||||
|
'custom_int5' => NULL,
|
||||||
|
'name_first' => NULL,
|
||||||
|
'name_last' => NULL,
|
||||||
|
'email_address' => NULL,
|
||||||
|
'merchant_id' => '10023100',
|
||||||
|
'signature' => '3ed27638479fd65cdffb0f4910679d10',
|
||||||
|
'q' => '/payment_notification_webhook/EhbnVYyzJZyccY85hcHIkIzNPI2rtHzznAyyyG73oSxZidAdN9gf8BvAKDomqeHp/4openRe7Az/WPe99p3eLy',
|
||||||
|
)
|
||||||
|
|
||||||
|
*/
|
||||||
|
public function paymentResponse(Request $request)
|
||||||
|
{
|
||||||
|
$response_array = $request->all();
|
||||||
|
|
||||||
|
$state = [
|
||||||
|
'server_response' => $request->all(),
|
||||||
|
'payment_hash' => $request->input('m_payment_id'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->payfast->payment_hash->data = array_merge((array) $this->payfast->payment_hash->data, $state);
|
||||||
|
$this->payfast->payment_hash->save();
|
||||||
|
|
||||||
|
if($response_array['payment_status'] == 'COMPLETE') {
|
||||||
|
|
||||||
|
$this->payfast->logSuccessfulGatewayResponse(['response' => $response_array, 'data' => $this->payfast->payment_hash], SystemLog::TYPE_PAYFAST);
|
||||||
|
|
||||||
|
return $this->processSuccessfulPayment($response_array);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->processUnsuccessfulPayment($response_array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processSuccessfulPayment($response_array)
|
||||||
|
{
|
||||||
|
|
||||||
|
$payment_record = [];
|
||||||
|
$payment_record['amount'] = $response_array['amount_gross'];
|
||||||
|
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
|
||||||
|
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||||
|
$payment_record['transaction_reference'] = $response_array['pf_payment_id'];
|
||||||
|
|
||||||
|
$payment = $this->payfast->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||||
|
|
||||||
|
return redirect()->route('client.payments.show', ['payment' => $this->payfast->encodePrimaryKey($payment->id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processUnsuccessfulPayment($server_response)
|
||||||
|
{
|
||||||
|
PaymentFailureMailer::dispatch($this->payfast->client, $server_response->cancellation_reason, $this->payfast->client->company, $server_response->amount);
|
||||||
|
|
||||||
|
PaymentFailureMailer::dispatch(
|
||||||
|
$this->payfast->client,
|
||||||
|
$server_response,
|
||||||
|
$this->payfast->client->company,
|
||||||
|
$server_response['amount_gross']
|
||||||
|
);
|
||||||
|
|
||||||
|
$message = [
|
||||||
|
'server_response' => $server_response,
|
||||||
|
'data' => $this->payfast->payment_hash->data,
|
||||||
|
];
|
||||||
|
|
||||||
|
SystemLogger::dispatch(
|
||||||
|
$message,
|
||||||
|
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||||
|
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||||
|
SystemLog::TYPE_PAYFAST,
|
||||||
|
$this->payfast->client,
|
||||||
|
$this->payfast->client->company,
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new PaymentFailed('Failed to process the payment.', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
184
app/PaymentDrivers/PayFast/Token.php
Normal file
184
app/PaymentDrivers/PayFast/Token.php
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\PaymentDrivers\PayFast;
|
||||||
|
|
||||||
|
use App\Exceptions\PaymentFailed;
|
||||||
|
use App\Jobs\Mail\PaymentFailureMailer;
|
||||||
|
use App\Jobs\Util\SystemLogger;
|
||||||
|
use App\Models\ClientGatewayToken;
|
||||||
|
use App\Models\GatewayType;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\PaymentHash;
|
||||||
|
use App\Models\PaymentType;
|
||||||
|
use App\Models\SystemLog;
|
||||||
|
use App\PaymentDrivers\PayFastPaymentDriver;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use GuzzleHttp\RequestOptions;
|
||||||
|
|
||||||
|
class Token
|
||||||
|
{
|
||||||
|
|
||||||
|
public $payfast;
|
||||||
|
|
||||||
|
//https://api.payfast.co.za/subscriptions/dc0521d3-55fe-269b-fa00-b647310d760f/adhoc
|
||||||
|
|
||||||
|
public function __construct(PayFastPaymentDriver $payfast)
|
||||||
|
{
|
||||||
|
$this->payfast = $payfast;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
// merchant-id
|
||||||
|
// integer, 8 char | REQUIRED
|
||||||
|
// Header, the Merchant ID as given by the PayFast system.
|
||||||
|
// version
|
||||||
|
// string | REQUIRED
|
||||||
|
// Header, the PayFast API version (i.e. v1).
|
||||||
|
// timestamp
|
||||||
|
// ISO-8601 date and time | REQUIRED
|
||||||
|
// Header, the current timestamp (YYYY-MM-DDTHH:MM:SS[+HH:MM]).
|
||||||
|
// signature
|
||||||
|
// string | REQUIRED
|
||||||
|
// Header, MD5 hash of the alphabetised submitted header and body variables, as well as the passphrase. Characters must be in lower case.
|
||||||
|
// amount
|
||||||
|
// integer | REQUIRED
|
||||||
|
// Body, the amount which the buyer must pay, in cents (ZAR), no decimals.
|
||||||
|
// item_name
|
||||||
|
// string, 100 char | REQUIRED
|
||||||
|
// Body, the name of the item being charged for.
|
||||||
|
// item_description
|
||||||
|
// string, 255 char | OPTIONAL
|
||||||
|
// Body, the description of the item being charged for.
|
||||||
|
// itn
|
||||||
|
// boolean | OPTIONAL
|
||||||
|
// Body, specify whether an ITN must be sent for the tokenization payment (true by default).
|
||||||
|
// m_payment_id
|
||||||
|
// string, 100 char | OPTIONAL
|
||||||
|
// Body, unique payment ID on the merchant’s system.
|
||||||
|
// cc_cvv
|
||||||
|
// numeric | OPTIONAL
|
||||||
|
|
||||||
|
|
||||||
|
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||||
|
{
|
||||||
|
|
||||||
|
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||||
|
$amount = round(($amount * pow(10, $this->payfast->client->currency()->precision)),0);
|
||||||
|
|
||||||
|
$header =[
|
||||||
|
'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||||
|
'timestamp' => now()->format('c'),
|
||||||
|
'version' => 'v1',
|
||||||
|
];
|
||||||
|
|
||||||
|
nlog($header);
|
||||||
|
|
||||||
|
$body = [
|
||||||
|
'amount' => $amount,
|
||||||
|
'item_name' => 'purchase',
|
||||||
|
'item_description' => ctrans('texts.invoices') . ': ' . collect($payment_hash->invoices())->pluck('invoice_number'),
|
||||||
|
'm_payment_id' => $payment_hash->hash,
|
||||||
|
'passphrase' => $this->payfast->company_gateway->getConfigField('passphrase'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$header['signature'] = $this->genSig(array_merge($header, $body));
|
||||||
|
|
||||||
|
nlog($header['signature']);
|
||||||
|
nlog($header['timestamp']);
|
||||||
|
nlog($this->payfast->company_gateway->getConfigField('merchantId'));
|
||||||
|
|
||||||
|
$result = $this->send($header, $body, $cgt->token);
|
||||||
|
|
||||||
|
nlog($result);
|
||||||
|
|
||||||
|
// /*Refactor and push to BaseDriver*/
|
||||||
|
// if ($data['response'] != null && $data['response']->getMessages()->getResultCode() == 'Ok') {
|
||||||
|
|
||||||
|
// $response = $data['response'];
|
||||||
|
|
||||||
|
// $this->storePayment($payment_hash, $data);
|
||||||
|
|
||||||
|
// $vars = [
|
||||||
|
// 'invoices' => $payment_hash->invoices(),
|
||||||
|
// 'amount' => $amount,
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// $logger_message = [
|
||||||
|
// 'server_response' => $response->getTransactionResponse()->getTransId(),
|
||||||
|
// 'data' => $this->formatGatewayResponse($data, $vars),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company);
|
||||||
|
|
||||||
|
// return true;
|
||||||
|
// } else {
|
||||||
|
|
||||||
|
// $vars = [
|
||||||
|
// 'invoices' => $payment_hash->invoices(),
|
||||||
|
// 'amount' => $amount,
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// $logger_message = [
|
||||||
|
// 'server_response' => $response->getTransactionResponse()->getTransId(),
|
||||||
|
// 'data' => $this->formatGatewayResponse($data, $vars),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// PaymentFailureMailer::dispatch($this->authorize->client, $response->getTransactionResponse()->getTransId(), $this->authorize->client->company, $amount);
|
||||||
|
|
||||||
|
// SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company);
|
||||||
|
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private function genSig($data)
|
||||||
|
{
|
||||||
|
$fields = [];
|
||||||
|
|
||||||
|
ksort($data);
|
||||||
|
|
||||||
|
foreach($data as $key => $value)
|
||||||
|
{
|
||||||
|
if (!empty($data[$key])) {
|
||||||
|
$fields[$key] = $data[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return md5(http_build_query($fields));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function send($headers, $body, $token)
|
||||||
|
{
|
||||||
|
$client = new \GuzzleHttp\Client(
|
||||||
|
[
|
||||||
|
'headers' => $headers,
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->post("https://api.payfast.co.za/subscriptions/{$token}/adhoc?testing=true",[
|
||||||
|
RequestOptions::JSON => ['body' => $body], RequestOptions::ALLOW_REDIRECTS => false
|
||||||
|
]);
|
||||||
|
|
||||||
|
return json_decode($response->getBody(),true);
|
||||||
|
}
|
||||||
|
catch(\Exception $e)
|
||||||
|
{
|
||||||
|
|
||||||
|
nlog($e->getMessage());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
202
app/PaymentDrivers/PayFastPaymentDriver.php
Normal file
202
app/PaymentDrivers/PayFastPaymentDriver.php
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\PaymentDrivers;
|
||||||
|
|
||||||
|
use App\Models\ClientGatewayToken;
|
||||||
|
use App\Models\GatewayType;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\PaymentHash;
|
||||||
|
use App\Models\SystemLog;
|
||||||
|
use App\PaymentDrivers\PayFast\CreditCard;
|
||||||
|
use App\PaymentDrivers\PayFast\Token;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class PayFastPaymentDriver extends BaseDriver
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public $refundable = false; //does this gateway support refunds?
|
||||||
|
|
||||||
|
public $token_billing = false; //does this gateway support token billing?
|
||||||
|
|
||||||
|
public $can_authorise_credit_card = true; //does this gateway support authorizations?
|
||||||
|
|
||||||
|
public $payfast; //initialized gateway
|
||||||
|
|
||||||
|
public $payment_method; //initialized payment method
|
||||||
|
|
||||||
|
public static $methods = [
|
||||||
|
GatewayType::CREDIT_CARD => CreditCard::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYFAST;
|
||||||
|
|
||||||
|
//developer resources
|
||||||
|
//https://sandbox.payfast.co.za/
|
||||||
|
|
||||||
|
public function gatewayTypes(): array
|
||||||
|
{
|
||||||
|
$types = [];
|
||||||
|
|
||||||
|
if($this->client->currency()->code == 'ZAR')
|
||||||
|
$types[] = GatewayType::CREDIT_CARD;
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function endpointUrl()
|
||||||
|
{
|
||||||
|
if($this->company_gateway->getConfigField('testMode'))
|
||||||
|
return 'https://sandbox.payfast.co.za/eng/process';
|
||||||
|
|
||||||
|
return 'https://www.payfast.co.za/eng/process';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
$this->payfast = new \PayFast\PayFastPayment(
|
||||||
|
[
|
||||||
|
'merchantId' => $this->company_gateway->getConfigField('merchantId'),
|
||||||
|
'merchantKey' => $this->company_gateway->getConfigField('merchantKey'),
|
||||||
|
'passPhrase' => $this->company_gateway->getConfigField('passPhrase'),
|
||||||
|
'testMode' => $this->company_gateway->getConfigField('testMode')
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch(Exception $e) {
|
||||||
|
|
||||||
|
echo '##PAYFAST## There was an exception: '.$e->getMessage();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPaymentMethod($payment_method_id)
|
||||||
|
{
|
||||||
|
$class = self::$methods[$payment_method_id];
|
||||||
|
$this->payment_method = new $class($this);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorizeView(array $data)
|
||||||
|
{
|
||||||
|
return $this->payment_method->authorizeView($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorizeResponse($request)
|
||||||
|
{
|
||||||
|
return $this->payment_method->authorizeResponse($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processPaymentView(array $data)
|
||||||
|
{
|
||||||
|
return $this->payment_method->paymentView($data); //this is your custom implementation from here
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processPaymentResponse($request)
|
||||||
|
{
|
||||||
|
return $this->payment_method->paymentResponse($request); //this is your custom implementation from here
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refund(Payment $payment, $amount, $return_client_response = false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||||
|
{
|
||||||
|
return (new Token($this))->tokenBilling($cgt, $payment_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateSignature($data)
|
||||||
|
{
|
||||||
|
$fields = array();
|
||||||
|
|
||||||
|
// specific order required by PayFast
|
||||||
|
// @see https://developers.payfast.co.za/documentation/#checkout-page
|
||||||
|
foreach (array('merchant_id', 'merchant_key', 'return_url', 'cancel_url', 'notify_url', 'name_first',
|
||||||
|
'name_last', 'email_address', 'cell_number',
|
||||||
|
/**
|
||||||
|
* Transaction Details
|
||||||
|
*/
|
||||||
|
'm_payment_id', 'amount', 'item_name', 'item_description',
|
||||||
|
/**
|
||||||
|
* Custom return data
|
||||||
|
*/
|
||||||
|
'custom_int1', 'custom_int2', 'custom_int3', 'custom_int4', 'custom_int5',
|
||||||
|
'custom_str1', 'custom_str2', 'custom_str3', 'custom_str4', 'custom_str5',
|
||||||
|
/**
|
||||||
|
* Email confirmation
|
||||||
|
*/
|
||||||
|
'email_confirmation', 'confirmation_address',
|
||||||
|
/**
|
||||||
|
* Payment Method
|
||||||
|
*/
|
||||||
|
'payment_method',
|
||||||
|
/**
|
||||||
|
* Subscriptions
|
||||||
|
*/
|
||||||
|
'subscription_type', 'billing_date', 'recurring_amount', 'frequency', 'cycles',
|
||||||
|
/**
|
||||||
|
* Passphrase for md5 signature generation
|
||||||
|
*/
|
||||||
|
'passphrase') as $key) {
|
||||||
|
if (!empty($data[$key])) {
|
||||||
|
$fields[$key] = $data[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return md5(http_build_query($fields));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function processWebhookRequest(Request $request, Payment $payment = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
$data = $request->all();
|
||||||
|
nlog($data);
|
||||||
|
|
||||||
|
if(array_key_exists('m_payment_id', $data))
|
||||||
|
{
|
||||||
|
|
||||||
|
$hash = Cache::get($data['m_payment_id']);
|
||||||
|
|
||||||
|
switch ($hash)
|
||||||
|
{
|
||||||
|
case 'cc_auth':
|
||||||
|
return $this->setPaymentMethod(GatewayType::CREDIT_CARD)
|
||||||
|
->authorizeResponse($request);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$data['m_payment_id']])->first();
|
||||||
|
|
||||||
|
return $this->setPaymentMethod(GatewayType::CREDIT_CARD)
|
||||||
|
->setPaymentHash($payment_hash)
|
||||||
|
->processPaymentResponse($request);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([], 200);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -195,7 +195,8 @@ class StripePaymentDriver extends BaseDriver
|
|||||||
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'];
|
if($this->company_gateway->require_postal_code)
|
||||||
|
$fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
|
||||||
if ($this->company_gateway->require_shipping_address) {
|
if ($this->company_gateway->require_shipping_address) {
|
||||||
$fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required'];
|
$fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required'];
|
||||||
|
@ -117,7 +117,6 @@ use WePayCommon;
|
|||||||
nlog("authorize the card first!");
|
nlog("authorize the card first!");
|
||||||
|
|
||||||
$response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', array(
|
$response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', array(
|
||||||
// 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]),
|
|
||||||
'client_id' => config('ninja.wepay.client_id'),
|
'client_id' => config('ninja.wepay.client_id'),
|
||||||
'client_secret' => config('ninja.wepay.client_secret'),
|
'client_secret' => config('ninja.wepay.client_secret'),
|
||||||
'credit_card_id' => (int)$request->input('credit_card_id'),
|
'credit_card_id' => (int)$request->input('credit_card_id'),
|
||||||
|
@ -14,6 +14,7 @@ namespace App\PaymentDrivers\WePay;
|
|||||||
use App\Exceptions\PaymentFailed;
|
use App\Exceptions\PaymentFailed;
|
||||||
use App\Jobs\Mail\PaymentFailureMailer;
|
use App\Jobs\Mail\PaymentFailureMailer;
|
||||||
use App\Jobs\Util\SystemLogger;
|
use App\Jobs\Util\SystemLogger;
|
||||||
|
use App\Models\GatewayType;
|
||||||
use App\Models\PaymentType;
|
use App\Models\PaymentType;
|
||||||
use App\Models\SystemLog;
|
use App\Models\SystemLog;
|
||||||
|
|
||||||
|
@ -58,7 +58,12 @@ class ClientRepository extends BaseRepository
|
|||||||
return $client;
|
return $client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!$client->id && auth()->user() && auth()->user()->company() && (!array_key_exists('country_id', $data) || empty($data['country_id']))){
|
||||||
|
$data['country_id'] = auth()->user()->company()->settings->country_id;
|
||||||
|
}
|
||||||
|
|
||||||
$client->fill($data);
|
$client->fill($data);
|
||||||
|
$client->save();
|
||||||
|
|
||||||
if (!isset($client->number) || empty($client->number)) {
|
if (!isset($client->number) || empty($client->number)) {
|
||||||
$client->number = $this->getNextClientNumber($client);
|
$client->number = $this->getNextClientNumber($client);
|
||||||
|
@ -80,6 +80,11 @@ class PaymentRepository extends BaseRepository {
|
|||||||
|
|
||||||
$client->service()->updatePaidToDate($data['amount'])->save();
|
$client->service()->updatePaidToDate($data['amount'])->save();
|
||||||
}
|
}
|
||||||
|
elseif($data['amount'] >0){
|
||||||
|
|
||||||
|
//this fixes an edge case with unapplied payments
|
||||||
|
$client->service()->updatePaidToDate($data['amount'])->save();
|
||||||
|
}
|
||||||
|
|
||||||
if (array_key_exists('credits', $data) && is_array($data['credits']) && count($data['credits']) > 0) {
|
if (array_key_exists('credits', $data) && is_array($data['credits']) && count($data['credits']) > 0) {
|
||||||
$_credit_totals = array_sum(array_column($data['credits'], 'amount'));
|
$_credit_totals = array_sum(array_column($data['credits'], 'amount'));
|
||||||
|
@ -104,6 +104,8 @@ class ApplyPayment extends AbstractService
|
|||||||
->ledger()
|
->ledger()
|
||||||
->updatePaymentBalance($amount_paid);
|
->updatePaymentBalance($amount_paid);
|
||||||
|
|
||||||
|
nlog("updating client balance by amount {$amount_paid}");
|
||||||
|
|
||||||
$this->invoice
|
$this->invoice
|
||||||
->client
|
->client
|
||||||
->service()
|
->service()
|
||||||
|
@ -71,6 +71,7 @@ class HandleReversal extends AbstractService
|
|||||||
$credit = CreditFactory::create($this->invoice->company_id, $this->invoice->user_id);
|
$credit = CreditFactory::create($this->invoice->company_id, $this->invoice->user_id);
|
||||||
$credit->client_id = $this->invoice->client_id;
|
$credit->client_id = $this->invoice->client_id;
|
||||||
$credit->invoice_id = $this->invoice->id;
|
$credit->invoice_id = $this->invoice->id;
|
||||||
|
$credit->date = now();
|
||||||
|
|
||||||
$item = InvoiceItemFactory::create();
|
$item = InvoiceItemFactory::create();
|
||||||
$item->quantity = 1;
|
$item->quantity = 1;
|
||||||
|
@ -33,13 +33,9 @@ class InvoiceService
|
|||||||
|
|
||||||
private $invoice;
|
private $invoice;
|
||||||
|
|
||||||
protected $client_service;
|
|
||||||
|
|
||||||
public function __construct($invoice)
|
public function __construct($invoice)
|
||||||
{
|
{
|
||||||
$this->invoice = $invoice;
|
$this->invoice = $invoice;
|
||||||
|
|
||||||
$this->client_service = new ClientService($invoice->client);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,7 +45,7 @@ class InvoiceService
|
|||||||
*/
|
*/
|
||||||
public function markPaid()
|
public function markPaid()
|
||||||
{
|
{
|
||||||
$this->invoice = (new MarkPaid($this->client_service, $this->invoice))->run();
|
$this->invoice = (new MarkPaid($this->invoice))->run();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -29,14 +29,10 @@ class MarkPaid extends AbstractService
|
|||||||
{
|
{
|
||||||
use GeneratesCounter;
|
use GeneratesCounter;
|
||||||
|
|
||||||
private $client_service;
|
|
||||||
|
|
||||||
private $invoice;
|
private $invoice;
|
||||||
|
|
||||||
public function __construct(ClientService $client_service, Invoice $invoice)
|
public function __construct(Invoice $invoice)
|
||||||
{
|
{
|
||||||
$this->client_service = $client_service;
|
|
||||||
|
|
||||||
$this->invoice = $invoice;
|
$this->invoice = $invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +88,9 @@ class MarkPaid extends AbstractService
|
|||||||
$payment->ledger()
|
$payment->ledger()
|
||||||
->updatePaymentBalance($payment->amount * -1);
|
->updatePaymentBalance($payment->amount * -1);
|
||||||
|
|
||||||
$this->client_service
|
$this->invoice
|
||||||
|
->client
|
||||||
|
->service()
|
||||||
->updateBalance($payment->amount * -1)
|
->updateBalance($payment->amount * -1)
|
||||||
->updatePaidToDate($payment->amount)
|
->updatePaidToDate($payment->amount)
|
||||||
->save();
|
->save();
|
||||||
|
@ -77,6 +77,7 @@ class SubscriptionService
|
|||||||
$recurring_invoice->next_send_date = now();
|
$recurring_invoice->next_send_date = now();
|
||||||
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
||||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||||
|
$recurring_invoice->auto_bill = $this->subscription->auto_bill;
|
||||||
|
|
||||||
/* Start the recurring service */
|
/* Start the recurring service */
|
||||||
$recurring_invoice->service()
|
$recurring_invoice->service()
|
||||||
@ -96,8 +97,6 @@ class SubscriptionService
|
|||||||
|
|
||||||
$response = $this->triggerWebhook($context);
|
$response = $this->triggerWebhook($context);
|
||||||
|
|
||||||
// nlog($response);
|
|
||||||
|
|
||||||
$this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
$this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -387,6 +386,7 @@ class SubscriptionService
|
|||||||
|
|
||||||
$pro_rata_charge_amount = 0;
|
$pro_rata_charge_amount = 0;
|
||||||
$pro_rata_refund_amount = 0;
|
$pro_rata_refund_amount = 0;
|
||||||
|
$is_credit = false;
|
||||||
|
|
||||||
$last_invoice = Invoice::where('subscription_id', $recurring_invoice->subscription_id)
|
$last_invoice = Invoice::where('subscription_id', $recurring_invoice->subscription_id)
|
||||||
->where('client_id', $recurring_invoice->client_id)
|
->where('client_id', $recurring_invoice->client_id)
|
||||||
@ -395,7 +395,22 @@ class SubscriptionService
|
|||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if($last_invoice->balance > 0)
|
if(!$last_invoice){
|
||||||
|
|
||||||
|
$is_credit = true;
|
||||||
|
|
||||||
|
$last_invoice = Credit::where('subscription_id', $recurring_invoice->subscription_id)
|
||||||
|
->where('client_id', $recurring_invoice->client_id)
|
||||||
|
->where('is_deleted', 0)
|
||||||
|
->withTrashed()
|
||||||
|
->orderBy('id', 'desc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice, $old_subscription);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif($last_invoice->balance > 0)
|
||||||
{
|
{
|
||||||
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||||
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||||
@ -410,7 +425,7 @@ class SubscriptionService
|
|||||||
|
|
||||||
nlog("total payable = {$total_payable}");
|
nlog("total payable = {$total_payable}");
|
||||||
|
|
||||||
$credit = $this->createCredit($last_invoice, $target_subscription);
|
$credit = $this->createCredit($last_invoice, $target_subscription, $is_credit);
|
||||||
|
|
||||||
$new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice);
|
$new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice);
|
||||||
|
|
||||||
@ -510,7 +525,7 @@ class SubscriptionService
|
|||||||
|
|
||||||
$total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price;
|
$total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price;
|
||||||
|
|
||||||
return $this->proRataInvoice($last_invoice, $target_subscription);
|
return $this->proRataInvoice($last_invoice, $target_subscription, $recurring_invoice->client_id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,26 +537,27 @@ class SubscriptionService
|
|||||||
*/
|
*/
|
||||||
private function handlePlanChange($payment_hash)
|
private function handlePlanChange($payment_hash)
|
||||||
{
|
{
|
||||||
nlog("handle plan change");
|
nlog("handle plan change");
|
||||||
|
|
||||||
$old_recurring_invoice = RecurringInvoice::find($payment_hash->data->billing_context->recurring_invoice);
|
$old_recurring_invoice = RecurringInvoice::find($payment_hash->data->billing_context->recurring_invoice);
|
||||||
|
|
||||||
$recurring_invoice = $this->createNewRecurringInvoice($old_recurring_invoice);
|
$recurring_invoice = $this->createNewRecurringInvoice($old_recurring_invoice);
|
||||||
|
|
||||||
$context = [
|
$context = [
|
||||||
'context' => 'change_plan',
|
'context' => 'change_plan',
|
||||||
'recurring_invoice' => $recurring_invoice->hashed_id,
|
'recurring_invoice' => $recurring_invoice->hashed_id,
|
||||||
'invoice' => $this->encodePrimaryKey($payment_hash->fee_invoice_id),
|
'invoice' => $this->encodePrimaryKey($payment_hash->fee_invoice_id),
|
||||||
'client' => $recurring_invoice->client->hashed_id,
|
'client' => $recurring_invoice->client->hashed_id,
|
||||||
'subscription' => $this->subscription->hashed_id,
|
'subscription' => $this->subscription->hashed_id,
|
||||||
'contact' => auth('contact')->user()->hashed_id,
|
'contact' => auth('contact')->user()->hashed_id,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
$response = $this->triggerWebhook($context);
|
$response = $this->triggerWebhook($context);
|
||||||
|
|
||||||
nlog($response);
|
nlog($response);
|
||||||
|
|
||||||
return $this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
return $this->handleRedirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,7 +568,7 @@ nlog("handle plan change");
|
|||||||
* @param RecurringInvoice $old_recurring_invoice
|
* @param RecurringInvoice $old_recurring_invoice
|
||||||
* @return RecurringInvoice
|
* @return RecurringInvoice
|
||||||
*/
|
*/
|
||||||
private function createNewRecurringInvoice($old_recurring_invoice) :RecurringInvoice
|
public function createNewRecurringInvoice($old_recurring_invoice) :RecurringInvoice
|
||||||
{
|
{
|
||||||
|
|
||||||
$old_recurring_invoice->service()->stop()->save();
|
$old_recurring_invoice->service()->stop()->save();
|
||||||
@ -581,9 +597,11 @@ nlog("handle plan change");
|
|||||||
* @param Subscription $target
|
* @param Subscription $target
|
||||||
* @return Credit
|
* @return Credit
|
||||||
*/
|
*/
|
||||||
private function createCredit($last_invoice, $target)
|
private function createCredit($last_invoice, $target, $is_credit = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
$last_invoice_is_credit = $is_credit ? false : true;
|
||||||
|
|
||||||
$subscription_repo = new SubscriptionRepository();
|
$subscription_repo = new SubscriptionRepository();
|
||||||
$credit_repo = new CreditRepository();
|
$credit_repo = new CreditRepository();
|
||||||
|
|
||||||
@ -593,7 +611,7 @@ nlog("handle plan change");
|
|||||||
|
|
||||||
$line_items = $subscription_repo->generateLineItems($target, false, true);
|
$line_items = $subscription_repo->generateLineItems($target, false, true);
|
||||||
|
|
||||||
$credit->line_items = array_merge($line_items, $this->calculateProRataRefundItems($last_invoice, true));
|
$credit->line_items = array_merge($line_items, $this->calculateProRataRefundItems($last_invoice, $last_invoice_is_credit));
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'client_id' => $last_invoice->client_id,
|
'client_id' => $last_invoice->client_id,
|
||||||
@ -613,19 +631,19 @@ nlog("handle plan change");
|
|||||||
* @param Subscription $target
|
* @param Subscription $target
|
||||||
* @return Invoice
|
* @return Invoice
|
||||||
*/
|
*/
|
||||||
private function proRataInvoice($last_invoice, $target)
|
private function proRataInvoice($last_invoice, $target, $client_id)
|
||||||
{
|
{
|
||||||
$subscription_repo = new SubscriptionRepository();
|
$subscription_repo = new SubscriptionRepository();
|
||||||
$invoice_repo = new InvoiceRepository();
|
$invoice_repo = new InvoiceRepository();
|
||||||
|
|
||||||
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
||||||
$invoice->date = now()->format('Y-m-d');
|
$invoice->date = now()->format('Y-m-d');
|
||||||
$invoice->subscription_id = $this->subscription->id;
|
$invoice->subscription_id = $target->id;
|
||||||
|
|
||||||
$invoice->line_items = array_merge($subscription_repo->generateLineItems($target), $this->calculateProRataRefundItems($last_invoice));
|
$invoice->line_items = array_merge($subscription_repo->generateLineItems($target), $this->calculateProRataRefundItems($last_invoice));
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'client_id' => $last_invoice->client_id,
|
'client_id' => $client_id,
|
||||||
'quantity' => 1,
|
'quantity' => 1,
|
||||||
'date' => now()->format('Y-m-d'),
|
'date' => now()->format('Y-m-d'),
|
||||||
];
|
];
|
||||||
@ -694,10 +712,14 @@ nlog("handle plan change");
|
|||||||
*/
|
*/
|
||||||
public function triggerWebhook($context)
|
public function triggerWebhook($context)
|
||||||
{
|
{
|
||||||
|
nlog("trigger webook");
|
||||||
|
|
||||||
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url']) < 1) {
|
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url']) < 1) {
|
||||||
return ["message" => "Success", "status_code" => 200];
|
return ["message" => "Success", "status_code" => 200];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nlog("past first if");
|
||||||
|
|
||||||
$response = false;
|
$response = false;
|
||||||
|
|
||||||
$body = array_merge($context, [
|
$body = array_merge($context, [
|
||||||
@ -708,6 +730,8 @@ nlog("handle plan change");
|
|||||||
|
|
||||||
$response = $this->sendLoad($this->subscription, $body);
|
$response = $this->sendLoad($this->subscription, $body);
|
||||||
|
|
||||||
|
nlog("after response");
|
||||||
|
|
||||||
/* Append the response to the system logger body */
|
/* Append the response to the system logger body */
|
||||||
if(is_array($response)){
|
if(is_array($response)){
|
||||||
|
|
||||||
@ -731,6 +755,7 @@ nlog("handle plan change");
|
|||||||
$client->company,
|
$client->company,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
nlog("ready to fire back");
|
||||||
|
|
||||||
if(is_array($body))
|
if(is_array($body))
|
||||||
return $response;
|
return $response;
|
||||||
@ -905,4 +930,22 @@ nlog("handle plan change");
|
|||||||
|
|
||||||
return redirect($default_redirect);
|
return redirect($default_redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function planPaid($invoice)
|
||||||
|
{
|
||||||
|
$recurring_invoice_hashed_id = $invoice->recurring_invoice()->exists() ? $invoice->recurring_invoice->hashed_id : null;
|
||||||
|
|
||||||
|
$context = [
|
||||||
|
'context' => 'plan_paid',
|
||||||
|
'subscription' => $this->subscription->hashed_id,
|
||||||
|
'recurring_invoice' => $recurring_invoice_hashed_id,
|
||||||
|
'client' => $invoice->client->hashed_id,
|
||||||
|
'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->contacts->first() : false,
|
||||||
|
'invoice' => $invoice->hashed_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->triggerWebhook($context);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,9 +173,15 @@ class HtmlEngine
|
|||||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||||
$data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.balance_due')];
|
$data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.balance_due')];
|
||||||
} else {
|
} else {
|
||||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
|
||||||
$data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.balance_due')];
|
|
||||||
|
|
||||||
|
if($this->entity->status_id == 1){
|
||||||
|
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||||
|
$data['$balance_due_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.balance_due')];
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||||
|
$data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.balance_due')];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$data['$quote.balance_due'] = &$data['$balance_due'];
|
$data['$quote.balance_due'] = &$data['$balance_due'];
|
||||||
|
@ -26,9 +26,9 @@ use App\Utils\CurlUtils;
|
|||||||
use App\Utils\HtmlEngine;
|
use App\Utils\HtmlEngine;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Response;
|
use Illuminate\Support\Facades\Response;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Phantom
|
class Phantom
|
||||||
|
@ -48,7 +48,7 @@ trait AppSetup
|
|||||||
$orderBy = 'num_days';
|
$orderBy = 'num_days';
|
||||||
} elseif ($name == 'fonts') {
|
} elseif ($name == 'fonts') {
|
||||||
$orderBy = 'sort_order';
|
$orderBy = 'sort_order';
|
||||||
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
|
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks', 'timezones'])) {
|
||||||
$orderBy = 'name';
|
$orderBy = 'name';
|
||||||
} else {
|
} else {
|
||||||
$orderBy = 'id';
|
$orderBy = 'id';
|
||||||
|
@ -126,7 +126,7 @@ trait GeneratesCounter
|
|||||||
break;
|
break;
|
||||||
case Quote::class:
|
case Quote::class:
|
||||||
|
|
||||||
if ($this->hasSharedCounter($client))
|
if ($this->hasSharedCounter($client, 'quote'))
|
||||||
return 'invoice_number_counter';
|
return 'invoice_number_counter';
|
||||||
|
|
||||||
return 'quote_number_counter';
|
return 'quote_number_counter';
|
||||||
@ -138,7 +138,7 @@ trait GeneratesCounter
|
|||||||
return 'payment_number_counter';
|
return 'payment_number_counter';
|
||||||
break;
|
break;
|
||||||
case Credit::class:
|
case Credit::class:
|
||||||
if ($this->hasSharedCounter($client))
|
if ($this->hasSharedCounter($client, 'credit'))
|
||||||
return 'invoice_number_counter';
|
return 'invoice_number_counter';
|
||||||
|
|
||||||
return 'credit_number_counter';
|
return 'credit_number_counter';
|
||||||
@ -318,9 +318,13 @@ trait GeneratesCounter
|
|||||||
*
|
*
|
||||||
* @return bool True if has shared counter, False otherwise.
|
* @return bool True if has shared counter, False otherwise.
|
||||||
*/
|
*/
|
||||||
public function hasSharedCounter(Client $client) : bool
|
public function hasSharedCounter(Client $client, string $type = 'quote') : bool
|
||||||
{
|
{
|
||||||
return (bool) $client->getSetting('shared_invoice_quote_counter') || (bool) $client->getSetting('shared_invoice_credit_counter');
|
if($type == 'quote')
|
||||||
|
return (bool) $client->getSetting('shared_invoice_quote_counter');
|
||||||
|
|
||||||
|
if($type == 'credit')
|
||||||
|
return (bool) $client->getSetting('shared_invoice_credit_counter');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
"maennchen/zipstream-php": "^1.2",
|
"maennchen/zipstream-php": "^1.2",
|
||||||
"nwidart/laravel-modules": "^8.0",
|
"nwidart/laravel-modules": "^8.0",
|
||||||
"omnipay/paypal": "^3.0",
|
"omnipay/paypal": "^3.0",
|
||||||
|
"payfast/payfast-php-sdk": "^1.1",
|
||||||
"pragmarx/google2fa": "^8.0",
|
"pragmarx/google2fa": "^8.0",
|
||||||
"predis/predis": "^1.1",
|
"predis/predis": "^1.1",
|
||||||
"sentry/sentry-laravel": "^2",
|
"sentry/sentry-laravel": "^2",
|
||||||
@ -86,6 +87,7 @@
|
|||||||
"fakerphp/faker": "^1.14",
|
"fakerphp/faker": "^1.14",
|
||||||
"filp/whoops": "^2.7",
|
"filp/whoops": "^2.7",
|
||||||
"friendsofphp/php-cs-fixer": "^2.16",
|
"friendsofphp/php-cs-fixer": "^2.16",
|
||||||
|
"laravel/dusk": "^6.15",
|
||||||
"mockery/mockery": "^1.3.1",
|
"mockery/mockery": "^1.3.1",
|
||||||
"nunomaduro/collision": "^5.0",
|
"nunomaduro/collision": "^5.0",
|
||||||
"phpunit/phpunit": "^9.0",
|
"phpunit/phpunit": "^9.0",
|
||||||
|
242
composer.lock
generated
242
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "16a38ffa3774d9d28a9f4c49366baac0",
|
"content-hash": "d2beb37ff5fbee59ad4bb792e944eb10",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "asm/php-ansible",
|
"name": "asm/php-ansible",
|
||||||
@ -159,16 +159,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "aws/aws-sdk-php",
|
"name": "aws/aws-sdk-php",
|
||||||
"version": "3.185.7",
|
"version": "3.185.10",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||||
"reference": "7c0cd260e749374b5df247c4768c8f33f9a604e4"
|
"reference": "667a83e4a18cb75db3ce74162efc97123da96261"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7c0cd260e749374b5df247c4768c8f33f9a604e4",
|
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/667a83e4a18cb75db3ce74162efc97123da96261",
|
||||||
"reference": "7c0cd260e749374b5df247c4768c8f33f9a604e4",
|
"reference": "667a83e4a18cb75db3ce74162efc97123da96261",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -243,9 +243,9 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.185.7"
|
"source": "https://github.com/aws/aws-sdk-php/tree/3.185.10"
|
||||||
},
|
},
|
||||||
"time": "2021-07-06T18:16:14+00:00"
|
"time": "2021-07-09T19:21:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
@ -4113,16 +4113,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/oauth1-client",
|
"name": "league/oauth1-client",
|
||||||
"version": "v1.9.0",
|
"version": "v1.9.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/thephpleague/oauth1-client.git",
|
"url": "https://github.com/thephpleague/oauth1-client.git",
|
||||||
"reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6"
|
"reference": "19a3ce488bb1547c906209e8293199ec34eaa5b1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/1e7e6be2dc543bf466236fb171e5b20e1b06aee6",
|
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/19a3ce488bb1547c906209e8293199ec34eaa5b1",
|
||||||
"reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6",
|
"reference": "19a3ce488bb1547c906209e8293199ec34eaa5b1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -4182,9 +4182,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/thephpleague/oauth1-client/issues",
|
"issues": "https://github.com/thephpleague/oauth1-client/issues",
|
||||||
"source": "https://github.com/thephpleague/oauth1-client/tree/v1.9.0"
|
"source": "https://github.com/thephpleague/oauth1-client/tree/v1.9.1"
|
||||||
},
|
},
|
||||||
"time": "2021-01-20T01:40:53+00:00"
|
"time": "2021-07-07T22:54:46+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/omnipay",
|
"name": "league/omnipay",
|
||||||
@ -4251,16 +4251,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/livewire",
|
"name": "livewire/livewire",
|
||||||
"version": "v2.5.1",
|
"version": "v2.5.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/livewire/livewire.git",
|
"url": "https://github.com/livewire/livewire.git",
|
||||||
"reference": "a4ffb135693e7982e5b982ca203f5dc7a7ae1126"
|
"reference": "1ca6757c78dbead4db7f52a72dabb8b27efcb3f6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/livewire/livewire/zipball/a4ffb135693e7982e5b982ca203f5dc7a7ae1126",
|
"url": "https://api.github.com/repos/livewire/livewire/zipball/1ca6757c78dbead4db7f52a72dabb8b27efcb3f6",
|
||||||
"reference": "a4ffb135693e7982e5b982ca203f5dc7a7ae1126",
|
"reference": "1ca6757c78dbead4db7f52a72dabb8b27efcb3f6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -4311,7 +4311,7 @@
|
|||||||
"description": "A front-end framework for Laravel.",
|
"description": "A front-end framework for Laravel.",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/livewire/livewire/issues",
|
"issues": "https://github.com/livewire/livewire/issues",
|
||||||
"source": "https://github.com/livewire/livewire/tree/v2.5.1"
|
"source": "https://github.com/livewire/livewire/tree/v2.5.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -4319,7 +4319,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2021-06-15T13:24:48+00:00"
|
"time": "2021-07-08T13:58:45+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "maennchen/zipstream-php",
|
"name": "maennchen/zipstream-php",
|
||||||
@ -5334,6 +5334,57 @@
|
|||||||
},
|
},
|
||||||
"time": "2020-10-15T08:29:30+00:00"
|
"time": "2020-10-15T08:29:30+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "payfast/payfast-php-sdk",
|
||||||
|
"version": "v1.1.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/PayFast/payfast-php-sdk.git",
|
||||||
|
"reference": "1372980e38f381b84eed7eb46a40d5819a4fe58c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/PayFast/payfast-php-sdk/zipball/1372980e38f381b84eed7eb46a40d5819a4fe58c",
|
||||||
|
"reference": "1372980e38f381b84eed7eb46a40d5819a4fe58c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"guzzlehttp/guzzle": ">=6.0.0",
|
||||||
|
"php": ">=7.2.5"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PayFast\\": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Claire Grant",
|
||||||
|
"email": "claire.grant@payfast.co.za"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PayFast PHP Library",
|
||||||
|
"keywords": [
|
||||||
|
"api",
|
||||||
|
"onsite",
|
||||||
|
"payfast",
|
||||||
|
"php"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/PayFast/payfast-php-sdk/issues",
|
||||||
|
"source": "https://github.com/PayFast/payfast-php-sdk/tree/v1.1.2"
|
||||||
|
},
|
||||||
|
"time": "2021-03-15T19:58:26+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "php-http/client-common",
|
"name": "php-http/client-common",
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
@ -7319,16 +7370,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stripe/stripe-php",
|
"name": "stripe/stripe-php",
|
||||||
"version": "v7.87.0",
|
"version": "v7.88.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/stripe/stripe-php.git",
|
"url": "https://github.com/stripe/stripe-php.git",
|
||||||
"reference": "9392f03cb8d8803bf8273378ce42d5cbbf1e24fc"
|
"reference": "7203d00ba9b09830c0c5d5c06a9558db43b8e0ea"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/9392f03cb8d8803bf8273378ce42d5cbbf1e24fc",
|
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/7203d00ba9b09830c0c5d5c06a9558db43b8e0ea",
|
||||||
"reference": "9392f03cb8d8803bf8273378ce42d5cbbf1e24fc",
|
"reference": "7203d00ba9b09830c0c5d5c06a9558db43b8e0ea",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -7374,9 +7425,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/stripe/stripe-php/issues",
|
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||||
"source": "https://github.com/stripe/stripe-php/tree/v7.87.0"
|
"source": "https://github.com/stripe/stripe-php/tree/v7.88.0"
|
||||||
},
|
},
|
||||||
"time": "2021-06-30T18:22:47+00:00"
|
"time": "2021-07-09T20:01:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "swiftmailer/swiftmailer",
|
"name": "swiftmailer/swiftmailer",
|
||||||
@ -11899,6 +11950,79 @@
|
|||||||
},
|
},
|
||||||
"time": "2020-07-09T08:09:16+00:00"
|
"time": "2020-07-09T08:09:16+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/dusk",
|
||||||
|
"version": "v6.15.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/dusk.git",
|
||||||
|
"reference": "45b55fa20321086c4f8cc4e712cbe54db644e21c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/dusk/zipball/45b55fa20321086c4f8cc4e712cbe54db644e21c",
|
||||||
|
"reference": "45b55fa20321086c4f8cc4e712cbe54db644e21c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-zip": "*",
|
||||||
|
"illuminate/console": "^6.0|^7.0|^8.0",
|
||||||
|
"illuminate/support": "^6.0|^7.0|^8.0",
|
||||||
|
"nesbot/carbon": "^2.0",
|
||||||
|
"php": "^7.2|^8.0",
|
||||||
|
"php-webdriver/webdriver": "^1.9.0",
|
||||||
|
"symfony/console": "^4.3|^5.0",
|
||||||
|
"symfony/finder": "^4.3|^5.0",
|
||||||
|
"symfony/process": "^4.3|^5.0",
|
||||||
|
"vlucas/phpdotenv": "^3.0|^4.0|^5.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.0",
|
||||||
|
"orchestra/testbench": "^4.16|^5.17.1|^6.12.1",
|
||||||
|
"phpunit/phpunit": "^7.5.15|^8.4|^9.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "6.x-dev"
|
||||||
|
},
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Dusk\\DuskServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Dusk\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Laravel Dusk provides simple end-to-end testing and browser automation.",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"testing",
|
||||||
|
"webdriver"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/dusk/issues",
|
||||||
|
"source": "https://github.com/laravel/dusk/tree/v6.15.0"
|
||||||
|
},
|
||||||
|
"time": "2021-04-06T14:14:57+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "maximebf/debugbar",
|
"name": "maximebf/debugbar",
|
||||||
"version": "v1.16.5",
|
"version": "v1.16.5",
|
||||||
@ -12452,6 +12576,72 @@
|
|||||||
},
|
},
|
||||||
"time": "2020-10-14T08:39:05+00:00"
|
"time": "2020-10-14T08:39:05+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "php-webdriver/webdriver",
|
||||||
|
"version": "1.11.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-webdriver/php-webdriver.git",
|
||||||
|
"reference": "da16e39968f8dd5cfb7d07eef91dc2b731c69880"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/da16e39968f8dd5cfb7d07eef91dc2b731c69880",
|
||||||
|
"reference": "da16e39968f8dd5cfb7d07eef91dc2b731c69880",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-zip": "*",
|
||||||
|
"php": "^5.6 || ~7.0 || ^8.0",
|
||||||
|
"symfony/polyfill-mbstring": "^1.12",
|
||||||
|
"symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0"
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"facebook/webdriver": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.0",
|
||||||
|
"ondram/ci-detector": "^2.1 || ^3.5 || ^4.0",
|
||||||
|
"php-coveralls/php-coveralls": "^2.4",
|
||||||
|
"php-mock/php-mock-phpunit": "^1.1 || ^2.0",
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||||
|
"phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5",
|
||||||
|
"symfony/var-dumper": "^3.3 || ^4.0 || ^5.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-SimpleXML": "For Firefox profile creation"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Facebook\\WebDriver\\": "lib/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/Exception/TimeoutException.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
|
||||||
|
"homepage": "https://github.com/php-webdriver/php-webdriver",
|
||||||
|
"keywords": [
|
||||||
|
"Chromedriver",
|
||||||
|
"geckodriver",
|
||||||
|
"php",
|
||||||
|
"selenium",
|
||||||
|
"webdriver"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
|
||||||
|
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.11.1"
|
||||||
|
},
|
||||||
|
"time": "2021-05-21T15:12:49+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpdocumentor/reflection-common",
|
"name": "phpdocumentor/reflection-common",
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
@ -14691,5 +14881,5 @@
|
|||||||
"platform-dev": {
|
"platform-dev": {
|
||||||
"php": "^7.3|^7.4|^8.0"
|
"php": "^7.3|^7.4|^8.0"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.0.0"
|
"plugin-api-version": "2.1.0"
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ return [
|
|||||||
'prefix' => '',
|
'prefix' => '',
|
||||||
'prefix_indexes' => true,
|
'prefix_indexes' => true,
|
||||||
'strict' => env('DB_STRICT', false),
|
'strict' => env('DB_STRICT', false),
|
||||||
'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
|
// 'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
|
||||||
],
|
],
|
||||||
|
|
||||||
'sqlite' => [
|
'sqlite' => [
|
||||||
|
@ -109,7 +109,6 @@ return [
|
|||||||
'gelf' => [
|
'gelf' => [
|
||||||
'driver' => 'custom',
|
'driver' => 'custom',
|
||||||
|
|
||||||
|
|
||||||
'via' => \Hedii\LaravelGelfLogger\GelfLoggerFactory::class,
|
'via' => \Hedii\LaravelGelfLogger\GelfLoggerFactory::class,
|
||||||
|
|
||||||
// This optional option determines the processors that should be
|
// This optional option determines the processors that should be
|
||||||
|
@ -14,8 +14,8 @@ return [
|
|||||||
'require_https' => env('REQUIRE_HTTPS', true),
|
'require_https' => env('REQUIRE_HTTPS', true),
|
||||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||||
'app_version' => '5.2.11',
|
'app_version' => '5.2.13',
|
||||||
'app_tag' => '5.2.11',
|
'app_tag' => '5.2.13',
|
||||||
'minimum_client_version' => '5.0.16',
|
'minimum_client_version' => '5.0.16',
|
||||||
'terms_version' => '1.0.1',
|
'terms_version' => '1.0.1',
|
||||||
'api_secret' => env('API_SECRET', ''),
|
'api_secret' => env('API_SECRET', ''),
|
||||||
@ -82,6 +82,8 @@ return [
|
|||||||
'checkout' => env('CHECKOUT_KEYS', ''),
|
'checkout' => env('CHECKOUT_KEYS', ''),
|
||||||
'travis' => env('TRAVIS', false),
|
'travis' => env('TRAVIS', false),
|
||||||
'test_email' => env('TEST_EMAIL', 'test@example.com'),
|
'test_email' => env('TEST_EMAIL', 'test@example.com'),
|
||||||
|
'wepay' => env('WEPAY_KEYS', ''),
|
||||||
|
'braintree' => env('BRAINTREE_KEYS', ''),
|
||||||
],
|
],
|
||||||
'contact' => [
|
'contact' => [
|
||||||
'email' => env('MAIL_FROM_ADDRESS'),
|
'email' => env('MAIL_FROM_ADDRESS'),
|
||||||
|
10
cypress.json
10
cypress.json
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"video": false,
|
|
||||||
"baseUrl": "http://localhost:8080/",
|
|
||||||
"chromeWebSecurity": false,
|
|
||||||
"env": {
|
|
||||||
"runningEnvironment": "docker"
|
|
||||||
},
|
|
||||||
"viewportWidth": 1280,
|
|
||||||
"viewportHeight": 800
|
|
||||||
}
|
|
48
cypress/excluded/checkout_credit_card.spec.js
vendored
48
cypress/excluded/checkout_credit_card.spec.js
vendored
@ -1,48 +0,0 @@
|
|||||||
import { second } from '../fixtures/example.json';
|
|
||||||
|
|
||||||
describe('Checkout Credit Card Payments', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// cy.useGateway(second);
|
|
||||||
cy.clientLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to complete payment using checkout credit card', () => {
|
|
||||||
cy.visit('/client/invoices');
|
|
||||||
|
|
||||||
cy.get('#unpaid-checkbox').click();
|
|
||||||
|
|
||||||
cy.get('[data-cy=pay-now')
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.location('pathname').should('eq', '/client/invoices/payment');
|
|
||||||
|
|
||||||
cy.get('[data-cy=payment-methods-dropdown').click();
|
|
||||||
|
|
||||||
cy.get('[data-cy=payment-method')
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(8000);
|
|
||||||
|
|
||||||
cy.get('.cko-pay-now.show')
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(3000);
|
|
||||||
|
|
||||||
cy.getWithinIframe('[data-checkout="card-number"]').type(
|
|
||||||
'4242424242424242'
|
|
||||||
);
|
|
||||||
cy.getWithinIframe('[data-checkout="expiry-month"]').type('12');
|
|
||||||
cy.getWithinIframe('[data-checkout="expiry-year"]').type('30');
|
|
||||||
cy.getWithinIframe('[data-checkout="cvv"]').type('100');
|
|
||||||
|
|
||||||
cy.getWithinIframe('.form-submit')
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(5000);
|
|
||||||
cy.url().should('contain', '/client/payments');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Using fixtures to represent data",
|
|
||||||
"email": "hello@cypress.io",
|
|
||||||
"body": "Fixtures are a great way to mock data for responses to routes",
|
|
||||||
|
|
||||||
"first": "VolejRejNm",
|
|
||||||
"second": "Wpmbk5ezJn",
|
|
||||||
|
|
||||||
"url": "http://localhost:8000"
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
describe('Credits', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.clientLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show credits page', () => {
|
|
||||||
cy.visit('/client/credits');
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/credits');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show credits text', () => {
|
|
||||||
cy.visit('/client/credits');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('[data-ref=meta-title]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Credits');
|
|
||||||
});
|
|
||||||
|
|
||||||
/* it('should have required table elements', () => {
|
|
||||||
cy.visit('/client/credits');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('table.credits-table > tbody > tr')
|
|
||||||
.first()
|
|
||||||
.find('a')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'View')
|
|
||||||
.click()
|
|
||||||
.location()
|
|
||||||
.should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/credits/VolejRejNm');
|
|
||||||
});
|
|
||||||
});*/
|
|
||||||
});
|
|
@ -1,73 +0,0 @@
|
|||||||
context('Invoices', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.clientLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show invoices page', () => {
|
|
||||||
cy.visit('/client/invoices');
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/invoices');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show invoices text', () => {
|
|
||||||
cy.visit('/client/invoices');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('[data-ref=meta-title]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Invoices');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show download and pay now buttons', () => {
|
|
||||||
cy.visit('/client/invoices');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('button[value="download"]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Download');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('button[value="payment"]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Pay Now');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have per page options dropdown', () => {
|
|
||||||
cy.visit('/client/invoices');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('select')
|
|
||||||
.first()
|
|
||||||
.should('have.value', '10');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have required table elements', () => {
|
|
||||||
cy.visit('/client/invoices');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('table.invoices-table > tbody > tr')
|
|
||||||
.first()
|
|
||||||
.find('.button-link')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'View')
|
|
||||||
.click()
|
|
||||||
.location()
|
|
||||||
.should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/invoices/VolejRejNm');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter table content', () => {
|
|
||||||
cy.visit('/client/invoices');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('#paid-checkbox')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('table.invoices-table > tbody > tr')
|
|
||||||
.first()
|
|
||||||
.should('not.contain', 'Overdue');
|
|
||||||
});
|
|
||||||
});
|
|
48
cypress/integration/client_portal/login.spec.js
vendored
48
cypress/integration/client_portal/login.spec.js
vendored
@ -1,48 +0,0 @@
|
|||||||
context('Login', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('/client/login');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should type into login form elements', () => {
|
|
||||||
cy.get('#test_email')
|
|
||||||
.invoke('val')
|
|
||||||
.then(emailValue => {
|
|
||||||
cy.get('#email')
|
|
||||||
.type(emailValue)
|
|
||||||
.should('have.value', emailValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('#test_password')
|
|
||||||
.invoke('val')
|
|
||||||
.then(passwordValue => {
|
|
||||||
cy.get('#password')
|
|
||||||
.type(passwordValue)
|
|
||||||
.should('have.value', passwordValue);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should login into client portal', () => {
|
|
||||||
cy.get('#test_email')
|
|
||||||
.invoke('val')
|
|
||||||
.then(emailValue => {
|
|
||||||
cy.get('#test_password')
|
|
||||||
.invoke('val')
|
|
||||||
.then(passwordValue => {
|
|
||||||
cy.get('#email')
|
|
||||||
.type(emailValue)
|
|
||||||
.should('have.value', emailValue);
|
|
||||||
cy.get('#password')
|
|
||||||
.type(passwordValue)
|
|
||||||
.should('have.value', passwordValue);
|
|
||||||
cy.get('#loginBtn')
|
|
||||||
.contains('Login')
|
|
||||||
.click();
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq(
|
|
||||||
'/client/invoices'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,30 +0,0 @@
|
|||||||
context('Payment methods', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.clientLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show payment methods page', () => {
|
|
||||||
cy.visit('/client/payment_methods');
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/payment_methods');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show payment methods text', () => {
|
|
||||||
cy.visit('/client/payment_methods');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('[data-ref=meta-title]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Payment Method');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have per page options dropdown', () => {
|
|
||||||
cy.visit('/client/payment_methods');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('select')
|
|
||||||
.first()
|
|
||||||
.should('have.value', '10');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,30 +0,0 @@
|
|||||||
context('Payments', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.clientLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show payments page', () => {
|
|
||||||
cy.visit('/client/payments');
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/payments');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show payments text', () => {
|
|
||||||
cy.visit('/client/payments');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('[data-ref=meta-title]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Payments');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have per page options dropdown', () => {
|
|
||||||
cy.visit('/client/payments');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('select')
|
|
||||||
.first()
|
|
||||||
.should('have.value', '10');
|
|
||||||
});
|
|
||||||
});
|
|
73
cypress/integration/client_portal/quotes.spec.js
vendored
73
cypress/integration/client_portal/quotes.spec.js
vendored
@ -1,73 +0,0 @@
|
|||||||
describe('Quotes', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.clientLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show quotes page', () => {
|
|
||||||
cy.visit('/client/quotes');
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/quotes');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show quotes text', () => {
|
|
||||||
cy.visit('/client/quotes');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('[data-ref=meta-title]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Quotes');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show download and approve buttons', () => {
|
|
||||||
cy.visit('/client/quotes');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('button[value="download"]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Download');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('button[value="approve"]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Approve');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have per page options dropdown', () => {
|
|
||||||
cy.visit('/client/quotes');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('select')
|
|
||||||
.first()
|
|
||||||
.should('have.value', '10');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have required table elements', () => {
|
|
||||||
cy.visit('/client/quotes');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('table.quotes-table > tbody > tr')
|
|
||||||
.first()
|
|
||||||
.find('.button-link')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'View')
|
|
||||||
.click()
|
|
||||||
.location()
|
|
||||||
.should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/quotes/VolejRejNm');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter table content', () => {
|
|
||||||
cy.visit('/client/quotes');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('#draft-checkbox')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('table.quotes-table > tbody > tr')
|
|
||||||
.first()
|
|
||||||
.should('not.contain', 'Sent');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,31 +0,0 @@
|
|||||||
context('Recurring invoices', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.clientLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show recurring invoices page', () => {
|
|
||||||
cy.visit('/client/recurring_invoices');
|
|
||||||
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/recurring_invoices');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show reucrring invoices text', () => {
|
|
||||||
cy.visit('/client/recurring_invoices');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('[data-ref=meta-title]')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Recurring Invoices');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have per page options dropdown', () => {
|
|
||||||
cy.visit('/client/recurring_invoices');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('select')
|
|
||||||
.first()
|
|
||||||
.should('have.value', '10');
|
|
||||||
});
|
|
||||||
});
|
|
298
cypress/integration/examples/actions.spec.js
vendored
298
cypress/integration/examples/actions.spec.js
vendored
@ -1,298 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context('Actions', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/commands/actions')
|
|
||||||
})
|
|
||||||
|
|
||||||
// https://on.cypress.io/interacting-with-elements
|
|
||||||
|
|
||||||
it('.type() - type into a DOM element', () => {
|
|
||||||
// https://on.cypress.io/type
|
|
||||||
cy.get('.action-email')
|
|
||||||
.type('fake@email.com').should('have.value', 'fake@email.com')
|
|
||||||
|
|
||||||
// .type() with special character sequences
|
|
||||||
.type('{leftarrow}{rightarrow}{uparrow}{downarrow}')
|
|
||||||
.type('{del}{selectall}{backspace}')
|
|
||||||
|
|
||||||
// .type() with key modifiers
|
|
||||||
.type('{alt}{option}') //these are equivalent
|
|
||||||
.type('{ctrl}{control}') //these are equivalent
|
|
||||||
.type('{meta}{command}{cmd}') //these are equivalent
|
|
||||||
.type('{shift}')
|
|
||||||
|
|
||||||
// Delay each keypress by 0.1 sec
|
|
||||||
.type('slow.typing@email.com', { delay: 100 })
|
|
||||||
.should('have.value', 'slow.typing@email.com')
|
|
||||||
|
|
||||||
cy.get('.action-disabled')
|
|
||||||
// Ignore error checking prior to type
|
|
||||||
// like whether the input is visible or disabled
|
|
||||||
.type('disabled error checking', { force: true })
|
|
||||||
.should('have.value', 'disabled error checking')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.focus() - focus on a DOM element', () => {
|
|
||||||
// https://on.cypress.io/focus
|
|
||||||
cy.get('.action-focus').focus()
|
|
||||||
.should('have.class', 'focus')
|
|
||||||
.prev().should('have.attr', 'style', 'color: orange;')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.blur() - blur off a DOM element', () => {
|
|
||||||
// https://on.cypress.io/blur
|
|
||||||
cy.get('.action-blur').type('About to blur').blur()
|
|
||||||
.should('have.class', 'error')
|
|
||||||
.prev().should('have.attr', 'style', 'color: red;')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.clear() - clears an input or textarea element', () => {
|
|
||||||
// https://on.cypress.io/clear
|
|
||||||
cy.get('.action-clear').type('Clear this text')
|
|
||||||
.should('have.value', 'Clear this text')
|
|
||||||
.clear()
|
|
||||||
.should('have.value', '')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.submit() - submit a form', () => {
|
|
||||||
// https://on.cypress.io/submit
|
|
||||||
cy.get('.action-form')
|
|
||||||
.find('[type="text"]').type('HALFOFF')
|
|
||||||
cy.get('.action-form').submit()
|
|
||||||
.next().should('contain', 'Your form has been submitted!')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.click() - click on a DOM element', () => {
|
|
||||||
// https://on.cypress.io/click
|
|
||||||
cy.get('.action-btn').click()
|
|
||||||
|
|
||||||
// You can click on 9 specific positions of an element:
|
|
||||||
// -----------------------------------
|
|
||||||
// | topLeft top topRight |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | left center right |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | bottomLeft bottom bottomRight |
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
// clicking in the center of the element is the default
|
|
||||||
cy.get('#action-canvas').click()
|
|
||||||
|
|
||||||
cy.get('#action-canvas').click('topLeft')
|
|
||||||
cy.get('#action-canvas').click('top')
|
|
||||||
cy.get('#action-canvas').click('topRight')
|
|
||||||
cy.get('#action-canvas').click('left')
|
|
||||||
cy.get('#action-canvas').click('right')
|
|
||||||
cy.get('#action-canvas').click('bottomLeft')
|
|
||||||
cy.get('#action-canvas').click('bottom')
|
|
||||||
cy.get('#action-canvas').click('bottomRight')
|
|
||||||
|
|
||||||
// .click() accepts an x and y coordinate
|
|
||||||
// that controls where the click occurs :)
|
|
||||||
|
|
||||||
cy.get('#action-canvas')
|
|
||||||
.click(80, 75) // click 80px on x coord and 75px on y coord
|
|
||||||
.click(170, 75)
|
|
||||||
.click(80, 165)
|
|
||||||
.click(100, 185)
|
|
||||||
.click(125, 190)
|
|
||||||
.click(150, 185)
|
|
||||||
.click(170, 165)
|
|
||||||
|
|
||||||
// click multiple elements by passing multiple: true
|
|
||||||
cy.get('.action-labels>.label').click({ multiple: true })
|
|
||||||
|
|
||||||
// Ignore error checking prior to clicking
|
|
||||||
cy.get('.action-opacity>.btn').click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.dblclick() - double click on a DOM element', () => {
|
|
||||||
// https://on.cypress.io/dblclick
|
|
||||||
|
|
||||||
// Our app has a listener on 'dblclick' event in our 'scripts.js'
|
|
||||||
// that hides the div and shows an input on double click
|
|
||||||
cy.get('.action-div').dblclick().should('not.be.visible')
|
|
||||||
cy.get('.action-input-hidden').should('be.visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.rightclick() - right click on a DOM element', () => {
|
|
||||||
// https://on.cypress.io/rightclick
|
|
||||||
|
|
||||||
// Our app has a listener on 'contextmenu' event in our 'scripts.js'
|
|
||||||
// that hides the div and shows an input on right click
|
|
||||||
cy.get('.rightclick-action-div').rightclick().should('not.be.visible')
|
|
||||||
cy.get('.rightclick-action-input-hidden').should('be.visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.check() - check a checkbox or radio element', () => {
|
|
||||||
// https://on.cypress.io/check
|
|
||||||
|
|
||||||
// By default, .check() will check all
|
|
||||||
// matching checkbox or radio elements in succession, one after another
|
|
||||||
cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]')
|
|
||||||
.check().should('be.checked')
|
|
||||||
|
|
||||||
cy.get('.action-radios [type="radio"]').not('[disabled]')
|
|
||||||
.check().should('be.checked')
|
|
||||||
|
|
||||||
// .check() accepts a value argument
|
|
||||||
cy.get('.action-radios [type="radio"]')
|
|
||||||
.check('radio1').should('be.checked')
|
|
||||||
|
|
||||||
// .check() accepts an array of values
|
|
||||||
cy.get('.action-multiple-checkboxes [type="checkbox"]')
|
|
||||||
.check(['checkbox1', 'checkbox2']).should('be.checked')
|
|
||||||
|
|
||||||
// Ignore error checking prior to checking
|
|
||||||
cy.get('.action-checkboxes [disabled]')
|
|
||||||
.check({ force: true }).should('be.checked')
|
|
||||||
|
|
||||||
cy.get('.action-radios [type="radio"]')
|
|
||||||
.check('radio3', { force: true }).should('be.checked')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.uncheck() - uncheck a checkbox element', () => {
|
|
||||||
// https://on.cypress.io/uncheck
|
|
||||||
|
|
||||||
// By default, .uncheck() will uncheck all matching
|
|
||||||
// checkbox elements in succession, one after another
|
|
||||||
cy.get('.action-check [type="checkbox"]')
|
|
||||||
.not('[disabled]')
|
|
||||||
.uncheck().should('not.be.checked')
|
|
||||||
|
|
||||||
// .uncheck() accepts a value argument
|
|
||||||
cy.get('.action-check [type="checkbox"]')
|
|
||||||
.check('checkbox1')
|
|
||||||
.uncheck('checkbox1').should('not.be.checked')
|
|
||||||
|
|
||||||
// .uncheck() accepts an array of values
|
|
||||||
cy.get('.action-check [type="checkbox"]')
|
|
||||||
.check(['checkbox1', 'checkbox3'])
|
|
||||||
.uncheck(['checkbox1', 'checkbox3']).should('not.be.checked')
|
|
||||||
|
|
||||||
// Ignore error checking prior to unchecking
|
|
||||||
cy.get('.action-check [disabled]')
|
|
||||||
.uncheck({ force: true }).should('not.be.checked')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.select() - select an option in a <select> element', () => {
|
|
||||||
// https://on.cypress.io/select
|
|
||||||
|
|
||||||
// at first, no option should be selected
|
|
||||||
cy.get('.action-select')
|
|
||||||
.should('have.value', '--Select a fruit--')
|
|
||||||
|
|
||||||
// Select option(s) with matching text content
|
|
||||||
cy.get('.action-select').select('apples')
|
|
||||||
// confirm the apples were selected
|
|
||||||
// note that each value starts with "fr-" in our HTML
|
|
||||||
cy.get('.action-select').should('have.value', 'fr-apples')
|
|
||||||
|
|
||||||
cy.get('.action-select-multiple')
|
|
||||||
.select(['apples', 'oranges', 'bananas'])
|
|
||||||
// when getting multiple values, invoke "val" method first
|
|
||||||
.invoke('val')
|
|
||||||
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
|
|
||||||
|
|
||||||
// Select option(s) with matching value
|
|
||||||
cy.get('.action-select').select('fr-bananas')
|
|
||||||
// can attach an assertion right away to the element
|
|
||||||
.should('have.value', 'fr-bananas')
|
|
||||||
|
|
||||||
cy.get('.action-select-multiple')
|
|
||||||
.select(['fr-apples', 'fr-oranges', 'fr-bananas'])
|
|
||||||
.invoke('val')
|
|
||||||
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
|
|
||||||
// assert the selected values include oranges
|
|
||||||
cy.get('.action-select-multiple')
|
|
||||||
.invoke('val').should('include', 'fr-oranges')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.scrollIntoView() - scroll an element into view', () => {
|
|
||||||
// https://on.cypress.io/scrollintoview
|
|
||||||
|
|
||||||
// normally all of these buttons are hidden,
|
|
||||||
// because they're not within
|
|
||||||
// the viewable area of their parent
|
|
||||||
// (we need to scroll to see them)
|
|
||||||
cy.get('#scroll-horizontal button')
|
|
||||||
.should('not.be.visible')
|
|
||||||
|
|
||||||
// scroll the button into view, as if the user had scrolled
|
|
||||||
cy.get('#scroll-horizontal button').scrollIntoView()
|
|
||||||
.should('be.visible')
|
|
||||||
|
|
||||||
cy.get('#scroll-vertical button')
|
|
||||||
.should('not.be.visible')
|
|
||||||
|
|
||||||
// Cypress handles the scroll direction needed
|
|
||||||
cy.get('#scroll-vertical button').scrollIntoView()
|
|
||||||
.should('be.visible')
|
|
||||||
|
|
||||||
cy.get('#scroll-both button')
|
|
||||||
.should('not.be.visible')
|
|
||||||
|
|
||||||
// Cypress knows to scroll to the right and down
|
|
||||||
cy.get('#scroll-both button').scrollIntoView()
|
|
||||||
.should('be.visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.trigger() - trigger an event on a DOM element', () => {
|
|
||||||
// https://on.cypress.io/trigger
|
|
||||||
|
|
||||||
// To interact with a range input (slider)
|
|
||||||
// we need to set its value & trigger the
|
|
||||||
// event to signal it changed
|
|
||||||
|
|
||||||
// Here, we invoke jQuery's val() method to set
|
|
||||||
// the value and trigger the 'change' event
|
|
||||||
cy.get('.trigger-input-range')
|
|
||||||
.invoke('val', 25)
|
|
||||||
.trigger('change')
|
|
||||||
.get('input[type=range]').siblings('p')
|
|
||||||
.should('have.text', '25')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.scrollTo() - scroll the window or element to a position', () => {
|
|
||||||
|
|
||||||
// https://on.cypress.io/scrollTo
|
|
||||||
|
|
||||||
// You can scroll to 9 specific positions of an element:
|
|
||||||
// -----------------------------------
|
|
||||||
// | topLeft top topRight |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | left center right |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | bottomLeft bottom bottomRight |
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
// if you chain .scrollTo() off of cy, we will
|
|
||||||
// scroll the entire window
|
|
||||||
cy.scrollTo('bottom')
|
|
||||||
|
|
||||||
cy.get('#scrollable-horizontal').scrollTo('right')
|
|
||||||
|
|
||||||
// or you can scroll to a specific coordinate:
|
|
||||||
// (x axis, y axis) in pixels
|
|
||||||
cy.get('#scrollable-vertical').scrollTo(250, 250)
|
|
||||||
|
|
||||||
// or you can scroll to a specific percentage
|
|
||||||
// of the (width, height) of the element
|
|
||||||
cy.get('#scrollable-both').scrollTo('75%', '25%')
|
|
||||||
|
|
||||||
// control the easing of the scroll (default is 'swing')
|
|
||||||
cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
|
|
||||||
|
|
||||||
// control the duration of the scroll (in ms)
|
|
||||||
cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
|
|
||||||
})
|
|
||||||
})
|
|
42
cypress/integration/examples/aliasing.spec.js
vendored
42
cypress/integration/examples/aliasing.spec.js
vendored
@ -1,42 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context('Aliasing', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/commands/aliasing')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.as() - alias a DOM element for later use', () => {
|
|
||||||
// https://on.cypress.io/as
|
|
||||||
|
|
||||||
// Alias a DOM element for use later
|
|
||||||
// We don't have to traverse to the element
|
|
||||||
// later in our code, we reference it with @
|
|
||||||
|
|
||||||
cy.get('.as-table').find('tbody>tr')
|
|
||||||
.first().find('td').first()
|
|
||||||
.find('button').as('firstBtn')
|
|
||||||
|
|
||||||
// when we reference the alias, we place an
|
|
||||||
// @ in front of its name
|
|
||||||
cy.get('@firstBtn').click()
|
|
||||||
|
|
||||||
cy.get('@firstBtn')
|
|
||||||
.should('have.class', 'btn-success')
|
|
||||||
.and('contain', 'Changed')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.as() - alias a route for later use', () => {
|
|
||||||
|
|
||||||
// Alias the route to wait for its response
|
|
||||||
cy.server()
|
|
||||||
cy.route('GET', 'comments/*').as('getComment')
|
|
||||||
|
|
||||||
// we have code that gets a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get('.network-btn').click()
|
|
||||||
|
|
||||||
// https://on.cypress.io/wait
|
|
||||||
cy.wait('@getComment').its('status').should('eq', 200)
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
168
cypress/integration/examples/assertions.spec.js
vendored
168
cypress/integration/examples/assertions.spec.js
vendored
@ -1,168 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context('Assertions', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/commands/assertions')
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Implicit Assertions', () => {
|
|
||||||
it('.should() - make an assertion about the current subject', () => {
|
|
||||||
// https://on.cypress.io/should
|
|
||||||
cy.get('.assertion-table')
|
|
||||||
.find('tbody tr:last')
|
|
||||||
.should('have.class', 'success')
|
|
||||||
.find('td')
|
|
||||||
.first()
|
|
||||||
// checking the text of the <td> element in various ways
|
|
||||||
.should('have.text', 'Column content')
|
|
||||||
.should('contain', 'Column content')
|
|
||||||
.should('have.html', 'Column content')
|
|
||||||
// chai-jquery uses "is()" to check if element matches selector
|
|
||||||
.should('match', 'td')
|
|
||||||
// to match text content against a regular expression
|
|
||||||
// first need to invoke jQuery method text()
|
|
||||||
// and then match using regular expression
|
|
||||||
.invoke('text')
|
|
||||||
.should('match', /column content/i)
|
|
||||||
|
|
||||||
// a better way to check element's text content against a regular expression
|
|
||||||
// is to use "cy.contains"
|
|
||||||
// https://on.cypress.io/contains
|
|
||||||
cy.get('.assertion-table')
|
|
||||||
.find('tbody tr:last')
|
|
||||||
// finds first <td> element with text content matching regular expression
|
|
||||||
.contains('td', /column content/i)
|
|
||||||
.should('be.visible')
|
|
||||||
|
|
||||||
// for more information about asserting element's text
|
|
||||||
// see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.and() - chain multiple assertions together', () => {
|
|
||||||
// https://on.cypress.io/and
|
|
||||||
cy.get('.assertions-link')
|
|
||||||
.should('have.class', 'active')
|
|
||||||
.and('have.attr', 'href')
|
|
||||||
.and('include', 'cypress.io')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Explicit Assertions', () => {
|
|
||||||
// https://on.cypress.io/assertions
|
|
||||||
it('expect - make an assertion about a specified subject', () => {
|
|
||||||
// We can use Chai's BDD style assertions
|
|
||||||
expect(true).to.be.true
|
|
||||||
const o = { foo: 'bar' }
|
|
||||||
|
|
||||||
expect(o).to.equal(o)
|
|
||||||
expect(o).to.deep.equal({ foo: 'bar' })
|
|
||||||
// matching text using regular expression
|
|
||||||
expect('FooBar').to.match(/bar$/i)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('pass your own callback function to should()', () => {
|
|
||||||
// Pass a function to should that can have any number
|
|
||||||
// of explicit assertions within it.
|
|
||||||
// The ".should(cb)" function will be retried
|
|
||||||
// automatically until it passes all your explicit assertions or times out.
|
|
||||||
cy.get('.assertions-p')
|
|
||||||
.find('p')
|
|
||||||
.should(($p) => {
|
|
||||||
// https://on.cypress.io/$
|
|
||||||
// return an array of texts from all of the p's
|
|
||||||
// @ts-ignore TS6133 unused variable
|
|
||||||
const texts = $p.map((i, el) => Cypress.$(el).text())
|
|
||||||
|
|
||||||
// jquery map returns jquery object
|
|
||||||
// and .get() convert this to simple array
|
|
||||||
const paragraphs = texts.get()
|
|
||||||
|
|
||||||
// array should have length of 3
|
|
||||||
expect(paragraphs, 'has 3 paragraphs').to.have.length(3)
|
|
||||||
|
|
||||||
// use second argument to expect(...) to provide clear
|
|
||||||
// message with each assertion
|
|
||||||
expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([
|
|
||||||
'Some text from first p',
|
|
||||||
'More text from second p',
|
|
||||||
'And even more text from third p',
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('finds element by class name regex', () => {
|
|
||||||
cy.get('.docs-header')
|
|
||||||
.find('div')
|
|
||||||
// .should(cb) callback function will be retried
|
|
||||||
.should(($div) => {
|
|
||||||
expect($div).to.have.length(1)
|
|
||||||
|
|
||||||
const className = $div[0].className
|
|
||||||
|
|
||||||
expect(className).to.match(/heading-/)
|
|
||||||
})
|
|
||||||
// .then(cb) callback is not retried,
|
|
||||||
// it either passes or fails
|
|
||||||
.then(($div) => {
|
|
||||||
expect($div, 'text content').to.have.text('Introduction')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can throw any error', () => {
|
|
||||||
cy.get('.docs-header')
|
|
||||||
.find('div')
|
|
||||||
.should(($div) => {
|
|
||||||
if ($div.length !== 1) {
|
|
||||||
// you can throw your own errors
|
|
||||||
throw new Error('Did not find 1 element')
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = $div[0].className
|
|
||||||
|
|
||||||
if (!className.match(/heading-/)) {
|
|
||||||
throw new Error(`Could not find class "heading-" in ${className}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('matches unknown text between two elements', () => {
|
|
||||||
/**
|
|
||||||
* Text from the first element.
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
let text
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes passed text,
|
|
||||||
* useful before comparing text with spaces and different capitalization.
|
|
||||||
* @param {string} s Text to normalize
|
|
||||||
*/
|
|
||||||
const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
|
|
||||||
|
|
||||||
cy.get('.two-elements')
|
|
||||||
.find('.first')
|
|
||||||
.then(($first) => {
|
|
||||||
// save text from the first element
|
|
||||||
text = normalizeText($first.text())
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get('.two-elements')
|
|
||||||
.find('.second')
|
|
||||||
.should(($div) => {
|
|
||||||
// we can massage text before comparing
|
|
||||||
const secondText = normalizeText($div.text())
|
|
||||||
|
|
||||||
expect(secondText, 'second text').to.equal(text)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('assert - assert shape of an object', () => {
|
|
||||||
const person = {
|
|
||||||
name: 'Joe',
|
|
||||||
age: 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.isObject(person, 'value is object')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
97
cypress/integration/examples/connectors.spec.js
vendored
97
cypress/integration/examples/connectors.spec.js
vendored
@ -1,97 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context('Connectors', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/commands/connectors')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.each() - iterate over an array of elements', () => {
|
|
||||||
// https://on.cypress.io/each
|
|
||||||
cy.get('.connectors-each-ul>li')
|
|
||||||
.each(($el, index, $list) => {
|
|
||||||
console.log($el, index, $list)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.its() - get properties on the current subject', () => {
|
|
||||||
// https://on.cypress.io/its
|
|
||||||
cy.get('.connectors-its-ul>li')
|
|
||||||
// calls the 'length' property yielding that value
|
|
||||||
.its('length')
|
|
||||||
.should('be.gt', 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.invoke() - invoke a function on the current subject', () => {
|
|
||||||
// our div is hidden in our script.js
|
|
||||||
// $('.connectors-div').hide()
|
|
||||||
|
|
||||||
// https://on.cypress.io/invoke
|
|
||||||
cy.get('.connectors-div').should('be.hidden')
|
|
||||||
// call the jquery method 'show' on the 'div.container'
|
|
||||||
.invoke('show')
|
|
||||||
.should('be.visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.spread() - spread an array as individual args to callback function', () => {
|
|
||||||
// https://on.cypress.io/spread
|
|
||||||
const arr = ['foo', 'bar', 'baz']
|
|
||||||
|
|
||||||
cy.wrap(arr).spread((foo, bar, baz) => {
|
|
||||||
expect(foo).to.eq('foo')
|
|
||||||
expect(bar).to.eq('bar')
|
|
||||||
expect(baz).to.eq('baz')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('.then()', () => {
|
|
||||||
it('invokes a callback function with the current subject', () => {
|
|
||||||
// https://on.cypress.io/then
|
|
||||||
cy.get('.connectors-list > li')
|
|
||||||
.then(($lis) => {
|
|
||||||
expect($lis, '3 items').to.have.length(3)
|
|
||||||
expect($lis.eq(0), 'first item').to.contain('Walk the dog')
|
|
||||||
expect($lis.eq(1), 'second item').to.contain('Feed the cat')
|
|
||||||
expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('yields the returned value to the next command', () => {
|
|
||||||
cy.wrap(1)
|
|
||||||
.then((num) => {
|
|
||||||
expect(num).to.equal(1)
|
|
||||||
|
|
||||||
return 2
|
|
||||||
})
|
|
||||||
.then((num) => {
|
|
||||||
expect(num).to.equal(2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('yields the original subject without return', () => {
|
|
||||||
cy.wrap(1)
|
|
||||||
.then((num) => {
|
|
||||||
expect(num).to.equal(1)
|
|
||||||
// note that nothing is returned from this callback
|
|
||||||
})
|
|
||||||
.then((num) => {
|
|
||||||
// this callback receives the original unchanged value 1
|
|
||||||
expect(num).to.equal(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('yields the value yielded by the last Cypress command inside', () => {
|
|
||||||
cy.wrap(1)
|
|
||||||
.then((num) => {
|
|
||||||
expect(num).to.equal(1)
|
|
||||||
// note how we run a Cypress command
|
|
||||||
// the result yielded by this Cypress command
|
|
||||||
// will be passed to the second ".then"
|
|
||||||
cy.wrap(2)
|
|
||||||
})
|
|
||||||
.then((num) => {
|
|
||||||
// this callback receives the value yielded by "cy.wrap(2)"
|
|
||||||
expect(num).to.equal(2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
78
cypress/integration/examples/cookies.spec.js
vendored
78
cypress/integration/examples/cookies.spec.js
vendored
@ -1,78 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context('Cookies', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
Cypress.Cookies.debug(true)
|
|
||||||
|
|
||||||
cy.visit('https://example.cypress.io/commands/cookies')
|
|
||||||
|
|
||||||
// clear cookies again after visiting to remove
|
|
||||||
// any 3rd party cookies picked up such as cloudflare
|
|
||||||
cy.clearCookies()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.getCookie() - get a browser cookie', () => {
|
|
||||||
// https://on.cypress.io/getcookie
|
|
||||||
cy.get('#getCookie .set-a-cookie').click()
|
|
||||||
|
|
||||||
// cy.getCookie() yields a cookie object
|
|
||||||
cy.getCookie('token').should('have.property', 'value', '123ABC')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.getCookies() - get browser cookies', () => {
|
|
||||||
// https://on.cypress.io/getcookies
|
|
||||||
cy.getCookies().should('be.empty')
|
|
||||||
|
|
||||||
cy.get('#getCookies .set-a-cookie').click()
|
|
||||||
|
|
||||||
// cy.getCookies() yields an array of cookies
|
|
||||||
cy.getCookies().should('have.length', 1).should((cookies) => {
|
|
||||||
|
|
||||||
// each cookie has these properties
|
|
||||||
expect(cookies[0]).to.have.property('name', 'token')
|
|
||||||
expect(cookies[0]).to.have.property('value', '123ABC')
|
|
||||||
expect(cookies[0]).to.have.property('httpOnly', false)
|
|
||||||
expect(cookies[0]).to.have.property('secure', false)
|
|
||||||
expect(cookies[0]).to.have.property('domain')
|
|
||||||
expect(cookies[0]).to.have.property('path')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.setCookie() - set a browser cookie', () => {
|
|
||||||
// https://on.cypress.io/setcookie
|
|
||||||
cy.getCookies().should('be.empty')
|
|
||||||
|
|
||||||
cy.setCookie('foo', 'bar')
|
|
||||||
|
|
||||||
// cy.getCookie() yields a cookie object
|
|
||||||
cy.getCookie('foo').should('have.property', 'value', 'bar')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.clearCookie() - clear a browser cookie', () => {
|
|
||||||
// https://on.cypress.io/clearcookie
|
|
||||||
cy.getCookie('token').should('be.null')
|
|
||||||
|
|
||||||
cy.get('#clearCookie .set-a-cookie').click()
|
|
||||||
|
|
||||||
cy.getCookie('token').should('have.property', 'value', '123ABC')
|
|
||||||
|
|
||||||
// cy.clearCookies() yields null
|
|
||||||
cy.clearCookie('token').should('be.null')
|
|
||||||
|
|
||||||
cy.getCookie('token').should('be.null')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.clearCookies() - clear browser cookies', () => {
|
|
||||||
// https://on.cypress.io/clearcookies
|
|
||||||
cy.getCookies().should('be.empty')
|
|
||||||
|
|
||||||
cy.get('#clearCookies .set-a-cookie').click()
|
|
||||||
|
|
||||||
cy.getCookies().should('have.length', 1)
|
|
||||||
|
|
||||||
// cy.clearCookies() yields null
|
|
||||||
cy.clearCookies()
|
|
||||||
|
|
||||||
cy.getCookies().should('be.empty')
|
|
||||||
})
|
|
||||||
})
|
|
222
cypress/integration/examples/cypress_api.spec.js
vendored
222
cypress/integration/examples/cypress_api.spec.js
vendored
@ -1,222 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context('Cypress.Commands', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
// https://on.cypress.io/custom-commands
|
|
||||||
|
|
||||||
it('.add() - create a custom command', () => {
|
|
||||||
Cypress.Commands.add('console', {
|
|
||||||
prevSubject: true,
|
|
||||||
}, (subject, method) => {
|
|
||||||
// the previous subject is automatically received
|
|
||||||
// and the commands arguments are shifted
|
|
||||||
|
|
||||||
// allow us to change the console method used
|
|
||||||
method = method || 'log'
|
|
||||||
|
|
||||||
// log the subject to the console
|
|
||||||
// @ts-ignore TS7017
|
|
||||||
console[method]('The subject is', subject)
|
|
||||||
|
|
||||||
// whatever we return becomes the new subject
|
|
||||||
// we don't want to change the subject so
|
|
||||||
// we return whatever was passed in
|
|
||||||
return subject
|
|
||||||
})
|
|
||||||
|
|
||||||
// @ts-ignore TS2339
|
|
||||||
cy.get('button').console('info').then(($button) => {
|
|
||||||
// subject is still $button
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
context('Cypress.Cookies', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
// https://on.cypress.io/cookies
|
|
||||||
it('.debug() - enable or disable debugging', () => {
|
|
||||||
Cypress.Cookies.debug(true)
|
|
||||||
|
|
||||||
// Cypress will now log in the console when
|
|
||||||
// cookies are set or cleared
|
|
||||||
cy.setCookie('fakeCookie', '123ABC')
|
|
||||||
cy.clearCookie('fakeCookie')
|
|
||||||
cy.setCookie('fakeCookie', '123ABC')
|
|
||||||
cy.clearCookie('fakeCookie')
|
|
||||||
cy.setCookie('fakeCookie', '123ABC')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.preserveOnce() - preserve cookies by key', () => {
|
|
||||||
// normally cookies are reset after each test
|
|
||||||
cy.getCookie('fakeCookie').should('not.be.ok')
|
|
||||||
|
|
||||||
// preserving a cookie will not clear it when
|
|
||||||
// the next test starts
|
|
||||||
cy.setCookie('lastCookie', '789XYZ')
|
|
||||||
Cypress.Cookies.preserveOnce('lastCookie')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('.defaults() - set defaults for all cookies', () => {
|
|
||||||
// now any cookie with the name 'session_id' will
|
|
||||||
// not be cleared before each new test runs
|
|
||||||
Cypress.Cookies.defaults({
|
|
||||||
whitelist: 'session_id',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
context('Cypress.Server', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Permanently override server options for
|
|
||||||
// all instances of cy.server()
|
|
||||||
|
|
||||||
// https://on.cypress.io/cypress-server
|
|
||||||
it('.defaults() - change default config of server', () => {
|
|
||||||
Cypress.Server.defaults({
|
|
||||||
delay: 0,
|
|
||||||
force404: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Cypress.arch', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Get CPU architecture name of underlying OS', () => {
|
|
||||||
// https://on.cypress.io/arch
|
|
||||||
expect(Cypress.arch).to.exist
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Cypress.config()', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Get and set configuration options', () => {
|
|
||||||
// https://on.cypress.io/config
|
|
||||||
let myConfig = Cypress.config()
|
|
||||||
|
|
||||||
expect(myConfig).to.have.property('animationDistanceThreshold', 5)
|
|
||||||
expect(myConfig).to.have.property('baseUrl', null)
|
|
||||||
expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
|
|
||||||
expect(myConfig).to.have.property('requestTimeout', 5000)
|
|
||||||
expect(myConfig).to.have.property('responseTimeout', 30000)
|
|
||||||
expect(myConfig).to.have.property('viewportHeight', 660)
|
|
||||||
expect(myConfig).to.have.property('viewportWidth', 1000)
|
|
||||||
expect(myConfig).to.have.property('pageLoadTimeout', 60000)
|
|
||||||
expect(myConfig).to.have.property('waitForAnimations', true)
|
|
||||||
|
|
||||||
expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
|
|
||||||
|
|
||||||
// this will change the config for the rest of your tests!
|
|
||||||
Cypress.config('pageLoadTimeout', 20000)
|
|
||||||
|
|
||||||
expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
|
|
||||||
|
|
||||||
Cypress.config('pageLoadTimeout', 60000)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Cypress.dom', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
// https://on.cypress.io/dom
|
|
||||||
it('.isHidden() - determine if a DOM element is hidden', () => {
|
|
||||||
let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
|
|
||||||
let visibleP = Cypress.$('.dom-p p.visible').get(0)
|
|
||||||
|
|
||||||
// our first paragraph has css class 'hidden'
|
|
||||||
expect(Cypress.dom.isHidden(hiddenP)).to.be.true
|
|
||||||
expect(Cypress.dom.isHidden(visibleP)).to.be.false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Cypress.env()', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
// We can set environment variables for highly dynamic values
|
|
||||||
|
|
||||||
// https://on.cypress.io/environment-variables
|
|
||||||
it('Get environment variables', () => {
|
|
||||||
// https://on.cypress.io/env
|
|
||||||
// set multiple environment variables
|
|
||||||
Cypress.env({
|
|
||||||
host: 'veronica.dev.local',
|
|
||||||
api_server: 'http://localhost:8888/v1/',
|
|
||||||
})
|
|
||||||
|
|
||||||
// get environment variable
|
|
||||||
expect(Cypress.env('host')).to.eq('veronica.dev.local')
|
|
||||||
|
|
||||||
// set environment variable
|
|
||||||
Cypress.env('api_server', 'http://localhost:8888/v2/')
|
|
||||||
expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
|
|
||||||
|
|
||||||
// get all environment variable
|
|
||||||
expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
|
|
||||||
expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Cypress.log', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Control what is printed to the Command Log', () => {
|
|
||||||
// https://on.cypress.io/cypress-log
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
context('Cypress.platform', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Get underlying OS name', () => {
|
|
||||||
// https://on.cypress.io/platform
|
|
||||||
expect(Cypress.platform).to.be.exist
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Cypress.version', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Get current version of Cypress being run', () => {
|
|
||||||
// https://on.cypress.io/version
|
|
||||||
expect(Cypress.version).to.be.exist
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context('Cypress.spec', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/cypress-api')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Get current spec information', () => {
|
|
||||||
// https://on.cypress.io/spec
|
|
||||||
// wrap the object so we can inspect it easily by clicking in the command log
|
|
||||||
cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute'])
|
|
||||||
})
|
|
||||||
})
|
|
114
cypress/integration/examples/files.spec.js
vendored
114
cypress/integration/examples/files.spec.js
vendored
@ -1,114 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
/// JSON fixture file can be loaded directly using
|
|
||||||
// the built-in JavaScript bundler
|
|
||||||
// @ts-ignore
|
|
||||||
const requiredExample = require('../../fixtures/example')
|
|
||||||
|
|
||||||
context('Files', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/commands/files')
|
|
||||||
})
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// load example.json fixture file and store
|
|
||||||
// in the test context object
|
|
||||||
cy.fixture('example.json').as('example')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.fixture() - load a fixture', () => {
|
|
||||||
// https://on.cypress.io/fixture
|
|
||||||
|
|
||||||
// Instead of writing a response inline you can
|
|
||||||
// use a fixture file's content.
|
|
||||||
|
|
||||||
cy.server()
|
|
||||||
cy.fixture('example.json').as('comment')
|
|
||||||
// when application makes an Ajax request matching "GET comments/*"
|
|
||||||
// Cypress will intercept it and reply with object
|
|
||||||
// from the "comment" alias
|
|
||||||
cy.route('GET', 'comments/*', '@comment').as('getComment')
|
|
||||||
|
|
||||||
// we have code that gets a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get('.fixture-btn').click()
|
|
||||||
|
|
||||||
cy.wait('@getComment').its('responseBody')
|
|
||||||
.should('have.property', 'name')
|
|
||||||
.and('include', 'Using fixtures to represent data')
|
|
||||||
|
|
||||||
// you can also just write the fixture in the route
|
|
||||||
cy.route('GET', 'comments/*', 'fixture:example.json').as('getComment')
|
|
||||||
|
|
||||||
// we have code that gets a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get('.fixture-btn').click()
|
|
||||||
|
|
||||||
cy.wait('@getComment').its('responseBody')
|
|
||||||
.should('have.property', 'name')
|
|
||||||
.and('include', 'Using fixtures to represent data')
|
|
||||||
|
|
||||||
// or write fx to represent fixture
|
|
||||||
// by default it assumes it's .json
|
|
||||||
cy.route('GET', 'comments/*', 'fx:example').as('getComment')
|
|
||||||
|
|
||||||
// we have code that gets a comment when
|
|
||||||
// the button is clicked in scripts.js
|
|
||||||
cy.get('.fixture-btn').click()
|
|
||||||
|
|
||||||
cy.wait('@getComment').its('responseBody')
|
|
||||||
.should('have.property', 'name')
|
|
||||||
.and('include', 'Using fixtures to represent data')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.fixture() or require - load a fixture', function () {
|
|
||||||
// we are inside the "function () { ... }"
|
|
||||||
// callback and can use test context object "this"
|
|
||||||
// "this.example" was loaded in "beforeEach" function callback
|
|
||||||
expect(this.example, 'fixture in the test context')
|
|
||||||
.to.deep.equal(requiredExample)
|
|
||||||
|
|
||||||
// or use "cy.wrap" and "should('deep.equal', ...)" assertion
|
|
||||||
// @ts-ignore
|
|
||||||
cy.wrap(this.example, 'fixture vs require')
|
|
||||||
.should('deep.equal', requiredExample)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.readFile() - read file contents', () => {
|
|
||||||
// https://on.cypress.io/readfile
|
|
||||||
|
|
||||||
// You can read a file and yield its contents
|
|
||||||
// The filePath is relative to your project's root.
|
|
||||||
cy.readFile('cypress.json').then((json) => {
|
|
||||||
expect(json).to.be.an('object')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.writeFile() - write to a file', () => {
|
|
||||||
// https://on.cypress.io/writefile
|
|
||||||
|
|
||||||
// You can write to a file
|
|
||||||
|
|
||||||
// Use a response from a request to automatically
|
|
||||||
// generate a fixture file for use later
|
|
||||||
cy.request('https://jsonplaceholder.cypress.io/users')
|
|
||||||
.then((response) => {
|
|
||||||
cy.writeFile('cypress/fixtures/users.json', response.body)
|
|
||||||
})
|
|
||||||
cy.fixture('users').should((users) => {
|
|
||||||
expect(users[0].name).to.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
// JavaScript arrays and objects are stringified
|
|
||||||
// and formatted into text.
|
|
||||||
cy.writeFile('cypress/fixtures/profile.json', {
|
|
||||||
id: 8739,
|
|
||||||
name: 'Jane',
|
|
||||||
email: 'jane@example.com',
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.fixture('profile').should((profile) => {
|
|
||||||
expect(profile.name).to.eq('Jane')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,52 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context('Local Storage', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/commands/local-storage')
|
|
||||||
})
|
|
||||||
// Although local storage is automatically cleared
|
|
||||||
// in between tests to maintain a clean state
|
|
||||||
// sometimes we need to clear the local storage manually
|
|
||||||
|
|
||||||
it('cy.clearLocalStorage() - clear all data in local storage', () => {
|
|
||||||
// https://on.cypress.io/clearlocalstorage
|
|
||||||
cy.get('.ls-btn').click().should(() => {
|
|
||||||
expect(localStorage.getItem('prop1')).to.eq('red')
|
|
||||||
expect(localStorage.getItem('prop2')).to.eq('blue')
|
|
||||||
expect(localStorage.getItem('prop3')).to.eq('magenta')
|
|
||||||
})
|
|
||||||
|
|
||||||
// clearLocalStorage() yields the localStorage object
|
|
||||||
cy.clearLocalStorage().should((ls) => {
|
|
||||||
expect(ls.getItem('prop1')).to.be.null
|
|
||||||
expect(ls.getItem('prop2')).to.be.null
|
|
||||||
expect(ls.getItem('prop3')).to.be.null
|
|
||||||
})
|
|
||||||
|
|
||||||
// Clear key matching string in Local Storage
|
|
||||||
cy.get('.ls-btn').click().should(() => {
|
|
||||||
expect(localStorage.getItem('prop1')).to.eq('red')
|
|
||||||
expect(localStorage.getItem('prop2')).to.eq('blue')
|
|
||||||
expect(localStorage.getItem('prop3')).to.eq('magenta')
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.clearLocalStorage('prop1').should((ls) => {
|
|
||||||
expect(ls.getItem('prop1')).to.be.null
|
|
||||||
expect(ls.getItem('prop2')).to.eq('blue')
|
|
||||||
expect(ls.getItem('prop3')).to.eq('magenta')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Clear keys matching regex in Local Storage
|
|
||||||
cy.get('.ls-btn').click().should(() => {
|
|
||||||
expect(localStorage.getItem('prop1')).to.eq('red')
|
|
||||||
expect(localStorage.getItem('prop2')).to.eq('blue')
|
|
||||||
expect(localStorage.getItem('prop3')).to.eq('magenta')
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.clearLocalStorage(/prop1|2/).should((ls) => {
|
|
||||||
expect(ls.getItem('prop1')).to.be.null
|
|
||||||
expect(ls.getItem('prop2')).to.be.null
|
|
||||||
expect(ls.getItem('prop3')).to.eq('magenta')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
32
cypress/integration/examples/location.spec.js
vendored
32
cypress/integration/examples/location.spec.js
vendored
@ -1,32 +0,0 @@
|
|||||||
/// <reference types="cypress" />
|
|
||||||
|
|
||||||
context('Location', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit('https://example.cypress.io/commands/location')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.hash() - get the current URL hash', () => {
|
|
||||||
// https://on.cypress.io/hash
|
|
||||||
cy.hash().should('be.empty')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.location() - get window.location', () => {
|
|
||||||
// https://on.cypress.io/location
|
|
||||||
cy.location().should((location) => {
|
|
||||||
expect(location.hash).to.be.empty
|
|
||||||
expect(location.href).to.eq('https://example.cypress.io/commands/location')
|
|
||||||
expect(location.host).to.eq('example.cypress.io')
|
|
||||||
expect(location.hostname).to.eq('example.cypress.io')
|
|
||||||
expect(location.origin).to.eq('https://example.cypress.io')
|
|
||||||
expect(location.pathname).to.eq('/commands/location')
|
|
||||||
expect(location.port).to.eq('')
|
|
||||||
expect(location.protocol).to.eq('https:')
|
|
||||||
expect(location.search).to.be.empty
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cy.url() - get the current URL', () => {
|
|
||||||
// https://on.cypress.io/url
|
|
||||||
cy.url().should('eq', 'https://example.cypress.io/commands/location')
|
|
||||||
})
|
|
||||||
})
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user