diff --git a/.travis.yml b/.travis.yml
index ff7262f87e92..d3d6ed0207ad 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -82,6 +82,7 @@ script:
- php ./vendor/codeception/codeception/codecept run --debug acceptance OnlinePaymentCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php
+ - php ./vendor/codeception/codeception/codecept run --debug acceptance GatewayFeesCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php
#- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env
diff --git a/app/Models/Traits/ChargesFees.php b/app/Models/Traits/ChargesFees.php
index 5b4a9fdf6034..ecea2a314158 100644
--- a/app/Models/Traits/ChargesFees.php
+++ b/app/Models/Traits/ChargesFees.php
@@ -29,18 +29,6 @@ trait ChargesFees
if ($settings->fee_percent) {
$amount = $this->partial > 0 ? $this->partial : $this->balance;
-
- // prevent charging taxes twice on the surcharge
- if ($location != FEE_LOCATION_ITEM) {
- if ($this->$taxField) {
- $taxAmount = 0;
- foreach ($this->getTaxes() as $key => $tax) {
- $taxAmount += $tax['amount'] - $tax['paid'];
- }
- $amount -= $taxAmount;
- }
- }
-
$fee += $amount * $settings->fee_percent / 100;
}
diff --git a/docs/api.rst b/docs/api.rst
index 33846951df33..6d9203a2c2aa 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -75,7 +75,7 @@ Optional Settings
The following are optional query parameter settings:
-- ``serializer``: Either array (the default) or json. If json is selected the data is returned using the `JSON API `_ format.
+- ``serializer``: Either array (the default) or `JSON `_.
- ``include``: A comma-separated list of nested relationships to include.
- ``client_id``: If set the results will be filtered by the client.
- ``page``: The page number of results to return when the results are paginated.
diff --git a/resources/views/accounts/payments.blade.php b/resources/views/accounts/payments.blade.php
index 198b7115c5fb..c92ab6e9017f 100644
--- a/resources/views/accounts/payments.blade.php
+++ b/resources/views/accounts/payments.blade.php
@@ -33,7 +33,7 @@
->help('gateway_fees_help')
->label('gateway_fees')!!}
- {!! Former::actions( Button::success(trans('texts.save'))->submit()->appendIcon(Icon::create('floppy-disk')) ) !!}
+ {!! Former::actions( Button::success(trans('texts.save'))->withAttributes(['id' => 'formSave'])->submit()->appendIcon(Icon::create('floppy-disk')) ) !!}
{!! Former::close() !!}
@@ -146,14 +146,14 @@
->onchange('onTaxRateChange(1)')
->addOption('', '')
->label(trans('texts.tax_rate'))
- ->fromQuery($taxRates, function($model) { return $model->name . ' ' . $model->rate . '%'; }, 'public_id') !!}
+ ->fromQuery($taxRates, function($model) { return $model->name . ': ' . $model->rate . '%'; }, 'public_id') !!}
@if ($account->enable_second_tax_rate)
{!! Former::select('tax_rate2')
->onchange('onTaxRateChange(2)')
->addOption('', '')
->label(trans('texts.tax_rate'))
- ->fromQuery($taxRates, function($model) { return $model->name . ' ' . $model->rate . '%'; }, 'public_id') !!}
+ ->fromQuery($taxRates, function($model) { return $model->name . ': ' . $model->rate . '%'; }, 'public_id') !!}
@endif
@endif
@@ -187,7 +187,7 @@
diff --git a/resources/views/accounts/product.blade.php b/resources/views/accounts/product.blade.php
index 7b6757bde4fd..8627e9a537aa 100644
--- a/resources/views/accounts/product.blade.php
+++ b/resources/views/accounts/product.blade.php
@@ -37,7 +37,7 @@
{!! Former::select('default_tax_rate_id')
->addOption('', '')
->label(trans('texts.tax_rate'))
- ->fromQuery($taxRates, function($model) { return $model->name . ' ' . $model->rate . '%'; }, 'id') !!}
+ ->fromQuery($taxRates, function($model) { return $model->name . ': ' . $model->rate . '%'; }, 'id') !!}
@endif
diff --git a/tests/_support/AcceptanceTester.php b/tests/_support/AcceptanceTester.php
index 8098444d2e9c..61af11e8123b 100644
--- a/tests/_support/AcceptanceTester.php
+++ b/tests/_support/AcceptanceTester.php
@@ -54,4 +54,94 @@ class AcceptanceTester extends \Codeception\Actor
$I->click("$dropdownSelector span.dropdown-toggle");
$I->click("$dropdownSelector ul li:nth-child($option)");
}
+
+ function createGateway(\AcceptanceTester $I)
+ {
+ if ( ! $I->grabFromDatabase('account_gateways', 'id', ['id' => 1])) {
+ $I->wantTo('create a gateway');
+ $I->amOnPage('/gateways/create?other_providers=true');
+ $I->fillField(['name' =>'23_apiKey'], env('stripe_secret_key') ?: Fixtures::get('stripe_secret_key'));
+ $I->fillField(['name' =>'publishable_key'], '');
+ $I->click('Save');
+ $I->see('Successfully created gateway');
+ }
+ }
+
+ function createClient(\AcceptanceTester $I, $email, $name = '')
+ {
+ $I->amOnPage('/clients/create');
+ $I->fillField(['name' => 'name'], $name);
+ $I->fillField(['name' => 'contacts[0][email]'], $email);
+ $I->click('Save');
+ $I->see($email);
+ }
+
+ function createProduct(\AcceptanceTester $I, $productKey, $cost, $taxName = '', $taxRate = '')
+ {
+ $I->amOnPage('/products/create');
+ $I->fillField(['name' => 'product_key'], $productKey);
+ $I->fillField(['name' => 'cost'], $cost);
+
+ if ($taxName && $taxRate) {
+ $taxOption = $taxName . ': ' . number_format($taxRate, 3) . '%';
+ $I->selectOption('#default_tax_rate_id', $taxOption);
+ }
+
+ $I->click('Save');
+ $I->wait(1);
+ //$I->see($productKey);
+ }
+
+ function createTaxRate(\AcceptanceTester $I, $name, $rate)
+ {
+ $I->amOnPage('/tax_rates/create');
+ $I->fillField(['name' => 'name'], $name);
+ $I->fillField(['name' => 'rate'], $rate);
+ $I->click('Save');
+ $I->see($name);
+ $I->see($rate);
+ }
+
+ function fillInvoice(\AcceptanceTester $I, $clientEmail, $productKey)
+ {
+ $I->amOnPage('/invoices/create');
+ $I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
+ $I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
+ $I->click('table.invoice-table tbody tr:nth-child(1) .tt-selectable');
+ }
+
+ function createOnlinePayment(\AcceptanceTester $I, $invoiceNumber)
+ {
+ $invoiceId = $I->grabFromDatabase('invoices', 'id', ['invoice_number' => $invoiceNumber]);
+ $invitationKey = $I->grabFromDatabase('invitations', 'invitation_key', ['invoice_id' => $invoiceId]);
+
+ $clientSession = $I->haveFriend('client');
+ $clientSession->does(function(AcceptanceTester $I) use ($invitationKey) {
+ $I->amOnPage('/view/' . $invitationKey);
+ $I->click('Pay Now');
+ $I->click('Credit Card');
+ $I->fillField('#card_number', '4242424242424242');
+ $I->fillField('#cvv', '100');
+ $I->selectOption('#expiration_month', 12);
+ $I->selectOption('#expiration_year', date('Y'));
+ $I->click('.btn-success');
+ $I->wait(3);
+ $I->see('Successfully applied payment');
+ });
+ }
+
+ function checkSettingOption(\AcceptanceTester $I, $url, $option)
+ {
+ $I->amOnPage('/settings/' . $url);
+ $I->checkOption('#' . $option);
+ $I->click('Save');
+ }
+
+ function uncheckSettingOption(\AcceptanceTester $I, $url, $option)
+ {
+ $I->amOnPage('/settings/' . $url);
+ $I->uncheckOption('#' . $option);
+ $I->click('Save');
+ }
+
}
diff --git a/tests/acceptance/GatewayFeesCest.php b/tests/acceptance/GatewayFeesCest.php
new file mode 100644
index 000000000000..dd3b32279ad0
--- /dev/null
+++ b/tests/acceptance/GatewayFeesCest.php
@@ -0,0 +1,191 @@
+checkIfLogin($I);
+
+ $this->faker = Factory::create();
+ }
+
+ public function checkLineItemFee(AcceptanceTester $I)
+ {
+ $clientName = $this->faker->word();
+ $clientEmail = $this->faker->safeEmail;
+ $productKey = $this->faker->word();
+ $taxName = $this->faker->word();
+
+ //$cost = $this->faker->randomFloat(2, 50, 100);
+ //$feeAmount = $this->faker->randomFloat(2, 1, 5);
+ //$taxRate = $this->faker->randomFloat(3, 5, 15);
+ //$feePercent = $this->faker->randomFloat(3, 1, 5);
+
+ $cost = 100;
+ $feeAmount = 10;
+ $taxRate = 10;
+ $feePercent = 10;
+
+ $total = $cost + ($cost * $taxRate / 100);
+ $fee = $feeAmount + ($total * $feeAmount / 100);
+ $feeWithTax = $fee + ($fee * $taxRate / 100);
+ $partialFee = $feeAmount + ($total / 2 * $feeAmount / 100);
+ $partialFeeWithTax = $partialFee + ($partialFee * $taxRate / 100);
+
+ // surcharge gateway fee
+ $this->configureSurchargeTaxRates($I, $taxName, $taxRate);
+ $this->configureFees($I, $feeAmount, $feePercent);
+
+ $I->createProduct($I, $productKey, $cost);
+ $I->createClient($I, $clientEmail, $clientName);
+
+ // without taxing the fee
+ $I->uncheckSettingOption($I, 'invoice_settings#invoice_surcharges', 'custom_invoice_taxes1');
+ $this->createInvoice($I, $clientName, $productKey, $total, $fee);
+
+ // with taxing the fee
+ $I->checkSettingOption($I, 'invoice_settings#invoice_surcharges', 'custom_invoice_taxes1');
+ $this->createInvoice($I, $clientName, $productKey, $total, $feeWithTax);
+
+
+ // line item gateway fee
+ $this->configureLineItemTaxRates($I, $taxName, $taxRate);
+ $productKey = $this->faker->word();
+ $I->createProduct($I, $productKey, $cost, $taxName, $taxRate);
+
+ // without taxing the fee
+ $this->configureGatewayFeeTax($I);
+ $this->createInvoice($I, $clientName, $productKey, $total, $fee);
+
+ // with taxing the fee
+ $this->configureGatewayFeeTax($I, $taxName, $taxRate);
+ $this->createInvoice($I, $clientName, $productKey, $total, $feeWithTax);
+
+
+ // partial invoice
+ $invoiceNumber = $this->createInvoice($I, $clientName, $productKey, $total, $partialFeeWithTax, $total / 2);
+ $this->createPayment($I, $invoiceNumber, $total + $partialFeeWithTax, 0, $partialFeeWithTax);
+ }
+
+ private function configureGatewayFeeTax($I, $taxName = '', $taxRate = '')
+ {
+ if ($taxName && $taxRate) {
+ $taxOption = $taxName . ': ' . number_format($taxRate, 3) . '%';
+ } else {
+ $taxOption = '';
+ }
+
+ $I->amOnPage('/settings/online_payments#fees');
+ $I->executeJS('javascript:showLimitsModal(\'Credit Card\', 1)');
+ $I->click('Fees');
+ $I->selectOption('tax_rate1', $taxOption);
+ $I->click('#modalSave');
+
+ $I->executeJS('javascript:showLimitsModal(\'Bank Transfer\', 2)');
+ $I->click('Fees');
+ $I->selectOption('tax_rate1', $taxOption);
+ $I->click('#modalSave');
+ }
+
+ private function configureSurchargeTaxRates($I, $taxName, $taxRate)
+ {
+ $taxOption = $taxName . ': ' . number_format($taxRate, 3) . '%';
+
+ $I->createTaxRate($I, $taxName, $taxRate);
+ $I->amOnPage('/settings/tax_rates');
+ $I->checkOption('#invoice_item_taxes');
+ $I->selectOption('default_tax_rate_id', $taxOption);
+ $I->click('Save');
+ }
+
+ private function configureLineItemTaxRates($I, $taxName, $taxRate)
+ {
+ $taxOption = $taxName . ': ' . number_format($taxRate, 3) . '%';
+
+ // set the gateway fee to use line items
+ $I->amOnPage('/settings/online_payments#fees');
+ $I->selectOption('gateway_fee_location', 'invoice_item');
+ $I->click('#formSave');
+
+ // disable the default invoice tax
+ $I->amOnPage('/settings/tax_rates');
+ $I->selectOption('#default_tax_rate_id', '');
+ $I->click('Save');
+ }
+
+ private function configureFees($I, $feeAmount, $feePercent)
+ {
+ $I->createGateway($I);
+
+ // enable gateway fees
+ $I->amOnPage('/settings/online_payments#fees');
+ $I->selectOption('gateway_fee_location', 'custom_value1');
+ $I->click('#formSave');
+
+ $I->executeJS('javascript:showLimitsModal(\'Credit Card\', 1)');
+ $I->click('Fees');
+ $I->fillField(['name' => 'fee_amount'], $feeAmount);
+ $I->fillField(['name' => 'fee_percent'], $feePercent);
+ $I->click('#modalSave');
+
+ $I->executeJS('javascript:showLimitsModal(\'Bank Transfer\', 2)');
+ $I->click('Fees');
+ $I->fillField(['name' => 'fee_amount'], $feeAmount * 2);
+ $I->fillField(['name' => 'fee_percent'], $feePercent * 2);
+ $I->click('#modalSave');
+ }
+
+ private function createInvoice($I, $clientEmail, $productKey, $amount, $fee, $partial = false)
+ {
+ $I->fillInvoice($I, $clientEmail, $productKey);
+ $invoiceNumber = $I->grabAttributeFrom('#invoice_number', 'value');
+
+ if ($partial) {
+ $amount = ($partial * 2);
+ $I->fillField('#partial', $partial);
+ }
+
+ $I->click('Mark Sent');
+ $I->see($clientEmail);
+
+ $balance = $partial ? ($amount - $partial) : 0;
+ $this->createPayment($I, $invoiceNumber, $amount, $balance, $fee);
+
+ return $invoiceNumber;
+ }
+
+ private function createPayment($I, $invoiceNumber, $amount, $balance, $fee)
+ {
+ $invoiceId = $I->grabFromDatabase('invoices', 'id', ['invoice_number' => $invoiceNumber]);
+ $invitationKey = $I->grabFromDatabase('invitations', 'invitation_key', ['invoice_id' => $invoiceId]);
+
+ // check we correctly remove/add back the gateway fee
+ $I->amOnPage('/view/' . $invitationKey);
+ $I->click('Pay Now');
+ $I->see('$' . number_format($fee, 2) . ' Fee');
+ $I->see('$' . number_format($fee * 2, 2) . ' Fee');
+
+ $I->amOnPage('/payment/' . $invitationKey . '/credit_card');
+ $I->amOnPage('/payment/' . $invitationKey . '/bank_transfer');
+ $I->amOnPage('/payment/' . $invitationKey . '/credit_card');
+
+ $I->amOnPage('/view/' . $invitationKey);
+ $I->click('Pay Now');
+ $I->see('$' . number_format($fee, 2) . ' Fee');
+ $I->see('$' . number_format($fee * 2, 2) . ' Fee');
+
+ $I->createOnlinePayment($I, $invoiceNumber);
+ $I->seeInDatabase('invoices', [
+ 'invoice_number' => $invoiceNumber,
+ 'amount' => ($amount + $fee),
+ 'balance' => $balance
+ ]);
+ }
+}
diff --git a/tests/acceptance/OnlinePaymentCest.php b/tests/acceptance/OnlinePaymentCest.php
index 7edf0aad1936..ad414d9ac5e7 100644
--- a/tests/acceptance/OnlinePaymentCest.php
+++ b/tests/acceptance/OnlinePaymentCest.php
@@ -21,23 +21,8 @@ class OnlinePaymentCest
$clientEmail = $this->faker->safeEmail;
$productKey = $this->faker->text(10);
- // set gateway info
- if ( ! $I->grabFromDatabase('account_gateways', 'id', ['id' => 1])) {
- $I->wantTo('create a gateway');
- $I->amOnPage('/gateways/create?other_providers=true');
-
- $I->fillField(['name' =>'23_apiKey'], env('stripe_secret_key') ?: Fixtures::get('stripe_secret_key'));
- // Fails to load StripeJS causing "ReferenceError: Can't find variable: Stripe"
- //$I->fillField(['name' =>'stripe_publishable_key'], env('stripe_secret_key') ?: Fixtures::get('stripe_publishable_key'));
- $I->click('Save');
- $I->see('Successfully created gateway');
- }
-
- // create client
- $I->amOnPage('/clients/create');
- $I->fillField(['name' => 'contacts[0][email]'], $clientEmail);
- $I->click('Save');
- $I->see($clientEmail);
+ $I->createGateway($I);
+ $I->createClient($I, $clientEmail);
// create product
$I->amOnPage('/products/create');
@@ -58,6 +43,10 @@ class OnlinePaymentCest
// enter payment
$clientId = $I->grabFromDatabase('contacts', 'client_id', ['email' => $clientEmail]);
+ $invoiceNumber = $I->grabFromDatabase('invoices', 'invoice_number', ['client_id' => $clientId]);
+ $I->createOnlinePayment($I, $invoiceNumber);
+
+ /*
$invoiceId = $I->grabFromDatabase('invoices', 'id', ['client_id' => $clientId]);
$invitationKey = $I->grabFromDatabase('invitations', 'invitation_key', ['invoice_id' => $invoiceId]);
@@ -66,18 +55,6 @@ class OnlinePaymentCest
$I->amOnPage('/view/' . $invitationKey);
$I->click('Pay Now');
$I->click('Credit Card');
-
- /*
- $I->fillField(['name' => 'first_name'], $this->faker->firstName);
- $I->fillField(['name' => 'last_name'], $this->faker->lastName);
- $I->fillField(['name' => 'address1'], $this->faker->streetAddress);
- $I->fillField(['name' => 'address2'], $this->faker->streetAddress);
- $I->fillField(['name' => 'city'], $this->faker->city);
- $I->fillField(['name' => 'state'], $this->faker->state);
- $I->fillField(['name' => 'postal_code'], $this->faker->postcode);
- $I->selectDropdown($I, 'United States', '.country-select .dropdown-toggle');
- */
-
$I->fillField('#card_number', '4242424242424242');
$I->fillField('#cvv', '100');
$I->selectOption('#expiration_month', 12);
@@ -86,12 +63,11 @@ class OnlinePaymentCest
$I->wait(3);
$I->see('Successfully applied payment');
});
-
$I->wait(1);
+ */
// create recurring invoice and auto-bill
$I->amOnPage('/recurring_invoices/create');
- //$I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
$I->selectDropdown($I, 'Test Test', '.client_select .dropdown-toggle');
$I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
$I->click('table.invoice-table tbody tr:nth-child(1) .tt-selectable');
@@ -99,6 +75,5 @@ class OnlinePaymentCest
$I->executeJS('onConfirmEmailClick()');
$I->wait(4);
$I->see("$0.00");
-
}
}
diff --git a/tests/acceptance/TaxRatesCest.php b/tests/acceptance/TaxRatesCest.php
index 48af53ebde15..5b491a83e62c 100644
--- a/tests/acceptance/TaxRatesCest.php
+++ b/tests/acceptance/TaxRatesCest.php
@@ -34,17 +34,8 @@ class TaxRatesCest
$invoiceTaxRate = number_format($invoiceTaxRate, 3);
// create tax rates
- $I->amOnPage('/tax_rates/create');
- $I->fillField(['name' => 'name'], $itemTaxName);
- $I->fillField(['name' => 'rate'], $itemTaxRate);
- $I->click('Save');
- $I->see($itemTaxName);
-
- $I->amOnPage('/tax_rates/create');
- $I->fillField(['name' => 'name'], $invoiceTaxName);
- $I->fillField(['name' => 'rate'], $invoiceTaxRate);
- $I->click('Save');
- $I->see($invoiceTaxName);
+ $I->createTaxRate($I, $itemTaxName, $itemTaxRate);
+ $I->createTaxRate($I, $invoiceTaxName, $invoiceTaxRate);
// enable line item taxes
$I->amOnPage('/settings/tax_rates');