Merge branch 'fraud-prevention-mechanism'

# Conflicts:
#	public/chunks/admin.js
#	public/chunks/payments/settings.js
#	public/chunks/platform.js
#	public/chunks/settings.js
#	public/chunks/status-check.js
#	public/css/tailwind.css
#	public/js/main.js
#	public/mix-manifest.json
#	src/App/Providers/AppServiceProvider.php
#	tests/Domain/Admin/AdminTest.php
This commit is contained in:
Čarodej
2022-06-30 10:54:13 +02:00
25 changed files with 536 additions and 105 deletions

View File

@@ -27,6 +27,49 @@ class AppServiceProvider extends ServiceProvider
// TODO: temporary
config()->set('session.lifetime', 15120);
// Set subscription config
$this->setSubscriptionConfig();
// Set app locale
$this->setLocale();
// Get all migrations with all directories
$this->setMigrations();
}
private function setMigrations(): void
{
$mainPath = database_path('migrations');
$directories = glob($mainPath . '/*', GLOB_ONLYDIR);
$this->loadMigrationsFrom(
array_merge([$mainPath], $directories)
);
}
private function setSubscriptionConfig(): void
{
if (app()->runningUnitTests()) {
return;
}
$settings = getAllSettings();
config([
'subscription.metered_billing.fraud_prevention_mechanism' => [
'usage_bigger_than_balance' => [
'active' => isset($settings->usage_bigger_than_balance) ? intval($settings->usage_bigger_than_balance) : true,
],
'limit_usage_in_new_accounts' => [
'active' => isset($settings->limit_usage_in_new_accounts) ? intval($settings->limit_usage_in_new_accounts) : true,
'amount' => isset($settings->limit_usage_in_new_accounts_amount) ? intval($settings->limit_usage_in_new_accounts_amount) : 20,
],
]
]);
}
private function setLocale(): void
{
try {
$app_locale = get_settings('language') ?? 'en';
} catch (\PDOException $e) {
@@ -38,21 +81,5 @@ class AppServiceProvider extends ServiceProvider
// Set locale for carbon dates
setlocale(LC_TIME, $app_locale . '_' . mb_strtoupper($app_locale));
// Get all migrations with all directories
$this->loadMigrationsFrom(
$this->get_migration_paths()
);
}
/**
* @return array
*/
private function get_migration_paths(): array
{
$mainPath = database_path('migrations');
$directories = glob($mainPath . '/*', GLOB_ONLYDIR);
return array_merge([$mainPath], $directories);
}
}

View File

@@ -3,6 +3,7 @@ namespace App\Users\Models;
use ByteUnits\Metric;
use Illuminate\Support\Str;
use BadMethodCallException;
use Domain\Files\Models\File;
use Domain\Folders\Models\Folder;
use Laravel\Sanctum\HasApiTokens;
@@ -196,10 +197,14 @@ class User extends Authenticatable implements MustVerifyEmail
public function __call($method, $parameters)
{
if (str_starts_with($method, 'can')) {
return resolve(RestrictionsManager::class)
->driver()
->$method($this, ...$parameters);
try {
if (str_starts_with($method, 'can') || str_starts_with($method, 'get')) {
return resolve(RestrictionsManager::class)
->driver()
->$method($this, ...$parameters);
}
} catch (BadMethodCallException $e) {
return parent::__call($method, $parameters);
}
return parent::__call($method, $parameters);

View File

@@ -69,6 +69,7 @@ class UserResource extends JsonResource
'canCreateFolder' => $this->canCreateFolder(),
'canCreateTeamFolder' => $this->canCreateTeamFolder(),
'canInviteTeamMembers' => $this->canInviteTeamMembers(),
'reason' => $this->getRestrictionReason(),
],
$this->mergeWhen($isFixedSubscription, fn () => [
'limitations' => $this->limitations->summary(),

View File

@@ -47,4 +47,9 @@ class DefaultRestrictionsEngine implements RestrictionsEngine
{
return true;
}
public function getRestrictionReason(User $user): string|null
{
return null;
}
}

View File

@@ -43,4 +43,9 @@ class FixedBillingRestrictionsEngine implements RestrictionsEngine
{
return true;
}
public function getRestrictionReason(User $user): string|null
{
return null;
}
}

View File

@@ -8,26 +8,46 @@ class MeteredBillingRestrictionsEngine implements RestrictionsEngine
{
public function canUpload(User $user, int $fileSize = 0): bool
{
// Check the count of the dunning emails
if ($this->getDunningSequenceCount($user) === 3) {
return false;
}
// Disable upload when user has more than 3 failed payments
return ! ($user->failedPayments()->count() >= 3);
return $this->checkFailedPayments($user);
}
public function canDownload(User $user): bool
{
// Check the count of the dunning emails
if ($this->getDunningSequenceCount($user) === 3) {
return false;
}
// Disable download when user has more than 3 failed payments
return ! ($user->failedPayments()->count() >= 3);
return $this->checkFailedPayments($user);
}
public function canCreateFolder(User $user): bool
{
// Check the count of the dunning emails
if ($this->getDunningSequenceCount($user) === 3) {
return false;
}
// Disable create folder when user has more than 3 failed payments
return ! ($user->failedPayments()->count() >= 3);
return $this->checkFailedPayments($user);
}
public function canCreateTeamFolder(User $user): bool
{
// Check the count of the dunning emails
if ($this->getDunningSequenceCount($user) === 3) {
return false;
}
// Disable create folder when user has more than 3 failed payments
return ! ($user->failedPayments()->count() >= 3);
return $this->checkFailedPayments($user);
}
public function canInviteTeamMembers(User $user, array $newInvites = []): bool
@@ -37,7 +57,38 @@ class MeteredBillingRestrictionsEngine implements RestrictionsEngine
public function canVisitShared(User $user): bool
{
// Check the count of the dunning emails
if ($this->getDunningSequenceCount($user) === 3) {
return false;
}
// Disable share visit when user has more than 3 failed payments
return ! ($user->failedPayments()->count() >= 3);
return $this->checkFailedPayments($user);
}
public function getRestrictionReason(User $user): string|null
{
if ($this->getDunningSequenceCount($user) === 3) {
return match ($user->dunning->type) {
'limit_usage_in_new_accounts' => 'Please make your first payment to cover your usage.',
'usage_bigger_than_balance' => 'Please increase your account balance higher than your monthly usage.',
};
}
if (! $this->checkFailedPayments($user)) {
return 'Please update your credit card to pay your usage.';
}
return null;
}
private function getDunningSequenceCount(User $user): int
{
return cache()->remember("dunning-count.$user->id", 3600, fn () => $user?->dunning->sequence ?? 0);
}
private function checkFailedPayments(User $user): bool
{
return cache()->remember("failed-payments-count.$user->id", 3600, fn () => !($user->failedPayments()->count() >= 3));
}
}

View File

@@ -16,4 +16,6 @@ interface RestrictionsEngine
public function canInviteTeamMembers(User $user, array $newInvites = []): bool;
public function canVisitShared(User $user): bool;
public function getRestrictionReason(User $user): string|null;
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Domain\Subscriptions\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use VueFileManager\Subscription\Domain\DunningEmails\Models\Dunning;
class DunningEmailToCoverAccountUsageNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
private Dunning $dunning
) {
}
public function via(): array
{
return ['mail', 'database', 'broadcast'];
}
public function toMail(): MailMessage
{
$message = $this->dunningMessages();
$index = $this->dunning->sequence - 1;
return (new MailMessage)
->subject($message[$this->dunning->type][$index]['subject'])
->greeting(__('Hi there'))
->line($message[$this->dunning->type][$index]['line'])
->action(__t('show_billing'), url('/user/settings/billing'))
->salutation(__('Regards'));
}
public function toArray(): array
{
$message = $this->dunningMessages();
$index = $this->dunning->sequence - 1;
return [
'category' => 'payment-alert',
'title' => $message[$this->dunning->type][$index]['subject'],
'description' => __t('dunning_notification_description'),
'action' => [
'type' => 'route',
'params' => [
'route' => __t('billing'),
'button' => __t('show_billing'),
],
],
];
}
private function dunningMessages(): array
{
return [
'limit_usage_in_new_accounts' => [
[
'subject' => __t('limit_usage_in_new_accounts_1_subject'),
'line' => __t('limit_usage_in_new_accounts_1_line'),
],
[
'subject' => __t('limit_usage_in_new_accounts_2_subject'),
'line' => __t('limit_usage_in_new_accounts_2_line'),
],
[
'subject' => __t('limit_usage_in_new_accounts_3_subject'),
'line' => __t('limit_usage_in_new_accounts_3_line'),
],
],
'usage_bigger_than_balance' => [
[
'subject' => __t("usage_bigger_than_balance_1_subject"),
'line' => __t('usage_bigger_than_balance_1_line'),
],
[
'subject' => __t('usage_bigger_than_balance_2_subject'),
'line' => __t('usage_bigger_than_balance_2_line'),
],
[
'subject' => __t('usage_bigger_than_balance_3_subject'),
'line' => __t('usage_bigger_than_balance_3_line'),
],
],
];
}
}