From 91d1a4ca4e7a3de9f37c44076484328f045c7a7b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 19 Mar 2017 15:31:25 +0200 Subject: [PATCH] Added gateway fee tests --- .travis.yml | 1 + app/Models/Traits/ChargesFees.php | 12 -- docs/api.rst | 2 +- resources/views/accounts/payments.blade.php | 8 +- resources/views/accounts/product.blade.php | 2 +- tests/_support/AcceptanceTester.php | 90 +++++++++ tests/acceptance/GatewayFeesCest.php | 191 ++++++++++++++++++++ tests/acceptance/OnlinePaymentCest.php | 39 +--- tests/acceptance/TaxRatesCest.php | 13 +- 9 files changed, 297 insertions(+), 61 deletions(-) create mode 100644 tests/acceptance/GatewayFeesCest.php 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');