1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-05 18:52:44 +01:00

Implement fees and limits map for company gateways (#3053)

* Add ability to remove group settings level company logo

* Company Gateway Fees and Limits

* Validation tests for FeesAndLimits

* Working on company gateways

* Working on transforming fees_and_limits in transformer

* Implement fees and limits map for company gateways
This commit is contained in:
David Bomba 2019-11-11 23:21:19 +11:00 committed by GitHub
parent 49ecde8a38
commit e4c18e734a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 378 additions and 83 deletions

View File

@ -0,0 +1,57 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\DataMapper;
class FeesAndLimits
{
public $min_limit = 0;
public $max_limit = 0;
public $fee_amount = 0;
public $fee_percent = 0;
public $fee_tax_name1 = '';
public $fee_tax_name2 = '';
public $fee_tax_name3 = '';
public $fee_tax_rate1 = 0;
public $fee_tax_rate2 = 0;
public $fee_tax_rate3 = 0;
public $fee_cap = 0;
public $adjust_fee_percent = false;
//public $gateway_type_id = 1;
public static $casts = [
'gateway_type_id' => 'int',
'min_limit' => 'float',
'max_limit' => 'float',
'fee_amount' => 'float',
'fee_percent' => 'float',
'fee_tax_name1' => 'string',
'fee_tax_name2' => 'string',
'fee_tax_name3' => 'string',
'fee_tax_rate1' => 'void',
'fee_tax_rate2' => 'float',
'fee_tax_rate3' => 'float',
'fee_cap' => 'float',
'adjust_fee_percent' => 'bool',
];
}

View File

@ -192,7 +192,7 @@ class CompanyGatewayController extends BaseController
*/
public function store(StoreCompanyGatewayRequest $request)
{
$company_gateway = CompanyGatewayFactory::create(auth()->user()->company()->id, auth()->user()->id);
$company_gateway->fill($request->all());
$company_gateway->save();
@ -370,6 +370,10 @@ class CompanyGatewayController extends BaseController
{
$company_gateway->fill($request->all());
if(!$request->has('fees_and_limits'))
$company_gateway->fees_and_limits = '';
$company_gateway->save();
return $this->itemResponse($company_gateway);

View File

@ -12,9 +12,12 @@
namespace App\Http\Requests\CompanyGateway;
use App\Http\Requests\Request;
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
class StoreCompanyGatewayRequest extends Request
{
use CompanyGatewayFeesAndLimitsSaver;
/**
* Determine if the user is authorized to make this request.
*
@ -30,10 +33,12 @@ class StoreCompanyGatewayRequest extends Request
public function rules()
{
$this->sanitize();
$rules = [
'gateway_key' => 'required'
'gateway_key' => 'required',
'fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(),
];
return $rules;
@ -43,22 +48,15 @@ class StoreCompanyGatewayRequest extends Request
{
$input = $this->all();
$input['config'] = encrypt($input['config']);
$input['min_limit'] = isset($input['fees_and_limits']['min_limit']) ? $input['fees_and_limits']['min_limit'] : null;
$input['max_limit'] = isset($input['fees_and_limits']['max_limit']) ? $input['fees_and_limits']['max_limit'] : null;
$input['fee_amount'] = isset($input['fees_and_limits']['fee_amount']) ? $input['fees_and_limits']['fee_amount'] : null;
$input['fee_percent'] = isset($input['fees_and_limits']['fee_percent']) ? $input['fees_and_limits']['fee_percent'] : null;
$input['fee_tax_name1'] = isset($input['fees_and_limits']['fee_tax_name1']) ? $input['fees_and_limits']['fee_tax_name1'] : '';
$input['fee_tax_name2'] = isset($input['fees_and_limits']['fee_tax_name2']) ? $input['fees_and_limits']['fee_tax_name2'] : '';
$input['fee_tax_name3'] = isset($input['fees_and_limits']['fee_tax_name3']) ? $input['fees_and_limits']['fee_tax_name3'] : '';
$input['fee_tax_rate1'] = isset($input['fees_and_limits']['fee_tax_rate1']) ? $input['fees_and_limits']['fee_tax_rate1'] : 0;
$input['fee_tax_rate2'] = isset($input['fees_and_limits']['fee_tax_rate2']) ? $input['fees_and_limits']['fee_tax_rate2'] : 0;
$input['fee_tax_rate3'] = isset($input['fees_and_limits']['fee_tax_rate3']) ? $input['fees_and_limits']['fee_tax_rate3'] : 0;
$input['fee_cap'] = isset($input['fees_and_limits']['fee_cap']) ? $input['fees_and_limits']['fee_cap'] : null;
$input['adjust_fee_percent'] = isset($input['fees_and_limits']['adjust_fee_percent']) ? $input['fees_and_limits']['adjust_fee_percent'] : 0;
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);
return $this->all();
}
}
}

View File

@ -12,10 +12,14 @@
namespace App\Http\Requests\CompanyGateway;
use App\Http\Requests\Request;
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
use App\Models\Company;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
class UpdateCompanyGatewayRequest extends Request
{
use CompanyGatewayFeesAndLimitsSaver;
/**
* Determine if the user is authorized to make this request.
*
@ -29,33 +33,25 @@ class UpdateCompanyGatewayRequest extends Request
public function rules()
{
$this->sanitize();
$rules = [
'fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(),
];
return $rules;
}
public function sanitize()
{
$input = $this->all();
$input['config'] = encrypt($input['config']);
$input['min_limit'] = isset($input['fees_and_limits']['min_limit']) ? $input['fees_and_limits']['min_limit'] : null;
$input['max_limit'] = isset($input['fees_and_limits']['max_limit']) ? $input['fees_and_limits']['max_limit'] : null;
$input['fee_amount'] = isset($input['fees_and_limits']['fee_amount']) ? $input['fees_and_limits']['fee_amount'] : null;
$input['fee_percent'] = isset($input['fees_and_limits']['fee_percent']) ? $input['fees_and_limits']['fee_percent'] : null;
$input['fee_tax_name1'] = isset($input['fees_and_limits']['fee_tax_name1']) ? $input['fees_and_limits']['fee_tax_name1'] : '';
$input['fee_tax_name2'] = isset($input['fees_and_limits']['fee_tax_name2']) ? $input['fees_and_limits']['fee_tax_name2'] : '';
$input['fee_tax_name3'] = isset($input['fees_and_limits']['fee_tax_name3']) ? $input['fees_and_limits']['fee_tax_name3'] : '';
$input['fee_tax_rate1'] = isset($input['fees_and_limits']['fee_tax_rate1']) ? $input['fees_and_limits']['fee_tax_rate1'] : 0;
$input['fee_tax_rate2'] = isset($input['fees_and_limits']['fee_tax_rate2']) ? $input['fees_and_limits']['fee_tax_rate2'] : 0;
$input['fee_tax_rate3'] = isset($input['fees_and_limits']['fee_tax_rate3']) ? $input['fees_and_limits']['fee_tax_rate3'] : 0;
$input['fee_cap'] = isset($input['fees_and_limits']['fee_cap']) ? $input['fees_and_limits']['fee_cap'] : null;
$input['adjust_fee_percent'] = isset($input['fees_and_limits']['adjust_fee_percent']) ? $input['fees_and_limits']['adjust_fee_percent'] : 0;
if(isset($input['fees_and_limits']))
$input['fees_and_limits'] = $this->cleanFeesAndLimits($input['fees_and_limits']);
$this->replace($input);
return $this->all();

View File

@ -31,6 +31,7 @@ class UpdateGroupSettingRequest extends Request
public function rules()
{
$this->sanitize();
$rules['settings'] = new ValidSettingsRule();
@ -38,6 +39,16 @@ class UpdateGroupSettingRequest extends Request
}
public function sanitize()
{
$input = $this->all();
$this->replace($input);
return $this->all();
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\ValidationRules;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
use Illuminate\Contracts\Validation\Rule;
/**
* Class ValidCompanyGatewayFeesAndLimitsRule
* @package App\Http\ValidationRules
*/
class ValidCompanyGatewayFeesAndLimitsRule implements Rule
{
use CompanyGatewayFeesAndLimitsSaver;
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public $return_data;
public function passes($attribute, $value)
{
$data = $this->validateFeesAndLimits($value);
if (is_array($data))
{
$this->return_data = $data;
return false;
}
else
return true;
}
/**
* @return string
*/
public function message()
{
return $this->return_data[0]." is not a valid ".$this->return_data[1];
}
}

View File

@ -141,7 +141,7 @@ class Company extends BaseModel
*/
public function company_gateways()
{
return $this->hasMany(CompanyGateway::class)->orderBy('priority','DESC');
return $this->hasMany(CompanyGateway::class);
}
/**

View File

@ -21,6 +21,13 @@ use Illuminate\Database\Eloquent\Model;
class CompanyGateway extends BaseModel
{
protected $casts = [
'fees_and_limits' => 'object',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
];
protected $fillable = [
'gateway_key',
'accepted_credit_cards',
@ -29,17 +36,7 @@ class CompanyGateway extends BaseModel
'show_shipping_address',
'update_details',
'config',
'priority',
'min_limit',
'max_limit',
'fee_amount',
'fee_percent',
'fee_tax_name1',
'fee_tax_name2',
'fee_tax_rate1',
'fee_tax_rate2',
'fee_cap',
'adjust_fee_percent',
'fees_and_limits',
];
public static $credit_cards = [
@ -50,6 +47,11 @@ class CompanyGateway extends BaseModel
16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover'],
];
// public function getFeesAndLimitsAttribute()
// {
// return json_decode($this->attributes['fees_and_limits']);
// }
public function company()
{
return $this->belongsTo(Company::class);

View File

@ -36,6 +36,16 @@ class GroupSettingRepository
$group_setting->fill($data);
$group_setting->save();
if(array_key_exists('company_logo', $data) && $data['company_logo'] == '')
{
$settings = $group_setting->settings;
unset($settings->company_logo);
$group_setting->settings = $settings;
$group_setting->save();
}
return $group_setting;
}

View File

@ -52,8 +52,7 @@ class CompanyGatewayTransformer extends EntityTransformer
'show_shipping_address' => (bool)$company_gateway->show_shipping_address,
'update_details' => (bool)$company_gateway->update_details,
'config' => (string) $company_gateway->getConfigTransformed(),
//'priority' => (int)$company_gateway->priority,
'fees_and_limits' => $this->mapFeesAndLimits($company_gateway),
'fees_and_limits' => $company_gateway->fees_and_limits ?: '',
'updated_at' => $company_gateway->updated_at,
'deleted_at' => $company_gateway->deleted_at,
];
@ -66,23 +65,4 @@ class CompanyGatewayTransformer extends EntityTransformer
return $this->includeItem($company_gateway->gateway, $transformer, Gateway::class);
}
private function mapFeesAndLimits($company_gateway)
{
$cg = new \stdClass;
$cg->min_limit = (float)$company_gateway->min_limit ?: null;
$cg->max_limit = (float)$company_gateway->max_limit ?: null;
$cg->fee_amount = (float) $company_gateway->fee_amount ?: null;
$cg->fee_percent = (float)$company_gateway->fee_percent ?: null;
$cg->fee_tax_name1 = (string)$company_gateway->fee_tax_name1 ?: '';
$cg->fee_tax_name2 = (string) $company_gateway->fee_tax_name2 ?: '';
$cg->fee_tax_name3 = (string) $company_gateway->fee_tax_name3 ?: '';
$cg->fee_tax_rate1 = (float) $company_gateway->fee_tax_rate1;
$cg->fee_tax_rate2 = (float)$company_gateway->fee_tax_rate2;
$cg->fee_tax_rate3 = (float)$company_gateway->fee_tax_rate3;
$cg->fee_cap = (float)$company_gateway->fee_cap ?: null;
$cg->adjust_fee_percent = (bool)$company_gateway->adjust_fee_percent;
return $cg;
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Utils\Traits;
use App\DataMapper\CompanySettings;
use App\DataMapper\FeesAndLimits;
/**
* Class CompanyGatewayFeesAndLimitsSaver
* @package App\Utils\Traits
*/
trait CompanyGatewayFeesAndLimitsSaver
{
public function validateFeesAndLimits($fees_and_limits)
{
$fees_and_limits = (object)$fees_and_limits;
$casts = FeesAndLimits::$casts;
foreach($fees_and_limits as $fee_and_limit)
{
$fee_and_limit = (object)$fee_and_limit;
foreach ($casts as $key => $value)
{
/* Handles unset settings or blank strings */
if(!property_exists($fee_and_limit, $key) || is_null($fee_and_limit->{$key}) || !isset($fee_and_limit->{$key}) || $fee_and_limit->{$key} == '')
continue;
/*Catch all filter */
if(!$this->checkAttribute($value, $fee_and_limit->{$key}))
return [$key, $value];
}
}
return true;
}
/**
* Type checks a object property.
* @param string $key The type
* @param string $value The object property
* @return bool TRUE if the property is the expected type
*/
private function checkAttribute($key, $value) :bool
{
switch ($key)
{
case 'int':
case 'integer':
return ctype_digit(strval($value));
case 'real':
case 'float':
case 'double':
return is_float($value) || is_numeric(strval($value));
case 'string':
return method_exists($value, '__toString' ) || is_null($value) || is_string($value);
case 'bool':
case 'boolean':
return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
case 'object':
return is_object($value);
case 'array':
return is_array($value);
case 'json':
json_decode($string);
return (json_last_error() == JSON_ERROR_NONE);
default:
return false;
}
}
public function cleanFeesAndLimits($fees_and_limits)
{
$new_arr = [];
foreach($fees_and_limits as $key => $value)
{
$fal = new FeesAndLimits;
foreach($value as $k => $v)
{
$fal->{$k} = $v;
}
$new_arr[$key] = (array)$fal;
}
return $new_arr;
}
}

View File

@ -379,20 +379,7 @@ class CreateUsersTable extends Migration
$table->boolean('show_shipping_address')->default(true)->nullable();
$table->boolean('update_details')->default(false)->nullable();
$table->text('config');
$table->unsignedInteger('priority')->default(0);
$table->decimal('min_limit', 16, 4)->nullable();
$table->decimal('max_limit', 16, 4)->nullable();
$table->decimal('fee_amount', 16, 4)->nullable();
$table->decimal('fee_percent', 16, 4)->nullable();
$table->string('fee_tax_name1')->nullable();
$table->string('fee_tax_name2')->nullable();
$table->string('fee_tax_name3')->nullable();
$table->decimal('fee_tax_rate1', 16, 4)->nullable();
$table->decimal('fee_tax_rate2', 16, 4)->nullable();
$table->decimal('fee_tax_rate3', 16, 4)->nullable();
$table->unsignedInteger('fee_cap')->nullable();
$table->boolean('adjust_fee_percent')->default(false);
$table->text('fees_and_limits');
$table->timestamps(6);
$table->softDeletes();

View File

@ -198,7 +198,6 @@ class RandomDataSeeder extends Seeder
$cg->show_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.stripe'));
$cg->priority = 1;
$cg->save();
$cg = new CompanyGateway;
@ -210,7 +209,6 @@ class RandomDataSeeder extends Seeder
$cg->show_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.stripe'));
$cg->priority = 2;
$cg->save();
}
@ -225,7 +223,6 @@ class RandomDataSeeder extends Seeder
$cg->show_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.paypal'));
$cg->priority = 3;
$cg->save();
}

View File

@ -3,11 +3,13 @@
namespace Tests\Feature;
use App\DataMapper\DefaultSettings;
use App\DataMapper\FeesAndLimits;
use App\Models\Account;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\User;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
use App\Utils\Traits\MakesHash;
use Faker\Factory;
use Illuminate\Database\Eloquent\Model;
@ -28,7 +30,7 @@ class CompanyGatewayApiTest extends TestCase
use MakesHash;
use DatabaseTransactions;
use MockAccountData;
use CompanyGatewayFeesAndLimitsSaver;
public function setUp() :void
{
@ -115,4 +117,93 @@ class CompanyGatewayApiTest extends TestCase
}
public function testCompanyGatewayFeesAndLimitsSuccess()
{
$fee = new FeesAndLimits;
$fee = (array)$fee;
$fee_and_limit['1'] = ['min_limit' => 1];
$fee_and_limit['2'] = ['min_limit' => 1];
$fee_and_limit['3'] = ['min_limit' => 1];
$fee_and_limit['4'] = ['min_limit' => 1];
$fee_and_limit['5'] = ['min_limit' => 1];
$data = [
'config' => 'random config',
'gateway_key' => '3b6621f970ab18887c4f6dca78d3f8bb',
'fees_and_limits' => $fee_and_limit,
];
/* POST */
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token
])->post('/api/v1/company_gateways', $data);
$cg = $response->json();
$cg_id = $cg['data']['id'];
$this->assertNotNull($cg_id);
$cg_fee = $cg['data']['fees_and_limits'];
$this->assertNotNull($cg_fee);
$response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token
])->get('/api/v1/company_gateways/'.$this->encodePrimaryKey($cg['data']['id']));
$cg = $response->json();
$response->assertStatus(200);
}
public function testCompanyGatewayFeesAndLimitsFails()
{
$fee_and_limit['bank_transfer'] = new FeesAndLimits;
$fee_and_limit['bank_transfer']->adjust_fee_percent = 10;
$data = [
'config' => 'random config',
'gateway_key' => '3b6621f970ab18887c4f6dca78d3f8bb',
'fees_and_limits' => $fee_and_limit,
];
/* POST */
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token
])->post('/api/v1/company_gateways', $data);
$response->assertStatus(302);
}
public function testCompanyGatewayArrayBuilder()
{
$arr = [
'min_limit' => 1,
'max_limit' => 2
];
$fal = (array)new FeesAndLimits;
$new_arr = array_replace($fal, $arr);
$this->assertEquals($arr['min_limit'], $new_arr['min_limit']);
$this->assertTrue(array_key_exists('fee_amount', $new_arr));
}
}

View File

@ -196,7 +196,6 @@ trait MockAccountData
$cg->show_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.stripe'));
$cg->priority = 1;
$cg->save();
@ -209,7 +208,6 @@ trait MockAccountData
$cg->show_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.stripe'));
$cg->priority = 2;
$cg->save();
}