- charge members

This commit is contained in:
Čarodej
2021-12-22 18:45:07 +01:00
parent b1cb7be678
commit 606765561d
13 changed files with 133 additions and 31 deletions
+9 -8
View File
@@ -208,14 +208,15 @@ return [
'interval.year' => 'Yearly',
// v2
'bandwidth' => 'Bandwidth',
'storage' => 'Storage',
'flatFee' => 'Flat Fee',
'feature_usage_desc_flatFee' => 'Price for the service.',
'feature_usage_desc_bandwidth' => 'Data amount you transferred to/from your account.',
'feature_usage_desc_storage' => 'Total storage amount you are using.',
'feature_usage_desc_members' => 'Total members you invited to your team folders.',
'feature_usage_desc_platform' => 'Total storage amount you are using.',
'bandwidth' => 'Bandwidth',
'storage' => 'Storage',
'flatFee' => 'Flat Fee',
'member' => 'Members',
'feature_usage_desc_flatFee' => 'Price for the service.',
'feature_usage_desc_bandwidth' => 'Data amount you transferred to/from your account.',
'feature_usage_desc_storage' => 'Total storage amount you are using.',
'feature_usage_desc_member' => 'Total members you invited to your team folders.',
'feature_usage_desc_platform' => 'Total storage amount you are using.',
],
'regular' => [
'actions.close' => 'Close',
+26 -8
View File
@@ -13,7 +13,7 @@
"/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared~1bec6fe4.js": "/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared~1bec6fe4.js?id=510e6c1b1017a73a40a6",
"/chunks/admin~chunks/platform.js": "/chunks/admin~chunks/platform.js?id=917aab9de16d3eb7039a",
"/chunks/admin~chunks/platform~chunks/settings.js": "/chunks/admin~chunks/platform~chunks/settings.js?id=4bde434e3ed10f3f29b2",
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.js?id=946723d9a3185230f2a6",
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.js?id=9bd311cf42f02c0709e8",
"/chunks/admin~chunks/platform~chunks/shared.js": "/chunks/admin~chunks/platform~chunks/shared.js?id=afeba4ebd13af7e995be",
"/chunks/app-appearance.js": "/chunks/app-appearance.js?id=6035ca411b2c4239d964",
"/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~5acee76d.js": "/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~5acee76d.js?id=75bbb477bf92edb65799",
@@ -56,10 +56,10 @@
"/chunks/pages.js": "/chunks/pages.js?id=7db66df0453135bf4e51",
"/chunks/plan.js": "/chunks/plan.js?id=f62a5bd64fb706b2f0e2",
"/chunks/plan-create.js": "/chunks/plan-create.js?id=995a9a5ae9cc43f35d2c",
"/chunks/plan-delete.js": "/chunks/plan-delete.js?id=c87a14fce5ffb4ea0451",
"/chunks/plan-settings.js": "/chunks/plan-settings.js?id=d58857bc3d9c578a1538",
"/chunks/plan-delete.js": "/chunks/plan-delete.js?id=2308117f74ce905c1c2a",
"/chunks/plan-settings.js": "/chunks/plan-settings.js?id=48f57902fbac955c7c99",
"/chunks/plan-subscribers.js": "/chunks/plan-subscribers.js?id=afa7c5328893c5d16e3b",
"/chunks/plans.js": "/chunks/plans.js?id=3c1f3d5a9cfe4dcf5237",
"/chunks/plans.js": "/chunks/plans.js?id=854c232c29d88c5b541a",
"/chunks/platform.js": "/chunks/platform.js?id=426da7075ef9a88ea088",
"/chunks/platform~chunks/shared.js": "/chunks/platform~chunks/shared.js?id=5734e9333fc67c706853",
"/chunks/platform~chunks/shared~chunks/shared-with-me~chunks/team-folders.js": "/chunks/platform~chunks/shared~chunks/shared-with-me~chunks/team-folders.js?id=7d983dfdc91de607d737",
@@ -73,7 +73,7 @@
"/chunks/settings-payment-methods.js": "/chunks/settings-payment-methods.js?id=13d23c92a535d7c9a4ff",
"/chunks/settings-storage.js": "/chunks/settings-storage.js?id=76b45c336e8e12b23e81",
"/chunks/settings-storage~chunks/settings-subscription~chunks/user-storage~chunks/user-subscription.js": "/chunks/settings-storage~chunks/settings-subscription~chunks/user-storage~chunks/user-subscription.js?id=cd797256cb819aac4d24",
"/chunks/settings-subscription.js": "/chunks/settings-subscription.js?id=5cbb63c9ce73aed42a38",
"/chunks/settings-subscription.js": "/chunks/settings-subscription.js?id=53a0c043797fd9a719bb",
"/chunks/settings~chunks/settings-password.js": "/chunks/settings~chunks/settings-password.js?id=743bf9cb1e62af56c04e",
"/chunks/setup-wizard.js": "/chunks/setup-wizard.js?id=651d5accf401908724c5",
"/chunks/shared.js": "/chunks/shared.js?id=6230d050545cd1bd9b87",
@@ -100,7 +100,7 @@
"/chunks/user-detail.js": "/chunks/user-detail.js?id=fab2eae409831e768b0d",
"/chunks/user-password.js": "/chunks/user-password.js?id=6aeb19839b38f287953d",
"/chunks/user-storage.js": "/chunks/user-storage.js?id=936f120357a4480e1bd5",
"/chunks/user-subscription.js": "/chunks/user-subscription.js?id=b868c2c3f6dce432c076",
"/chunks/user-subscription.js": "/chunks/user-subscription.js?id=781e3ce396f5daf7bba4",
"/chunks/users.js": "/chunks/users.js?id=ab7eeac6e8559dc1eb2b",
"/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~35bc7519.js": "/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~35bc7519.js?id=ae06aafc3749254fe4aa",
"/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~629342a0.js": "/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~629342a0.js?id=cdefaa7800d04dafb07b",
@@ -391,7 +391,7 @@
"/chunks/app-setup.bfb51d46fe9500c09f69.hot-update.js": "/chunks/app-setup.bfb51d46fe9500c09f69.hot-update.js",
"/chunks/plan.bfb51d46fe9500c09f69.hot-update.js": "/chunks/plan.bfb51d46fe9500c09f69.hot-update.js",
"/chunks/plan-create/fixed.js": "/chunks/plan-create/fixed.js?id=25ad60a594f9e25d7621",
"/chunks/plan-create/metered.js": "/chunks/plan-create/metered.js?id=e3ccb9876029e03fb932",
"/chunks/plan-create/metered.js": "/chunks/plan-create/metered.js?id=d41465e6cd6ff830e8c1",
"/chunks/settings-storage.bfb51d46fe9500c09f69.hot-update.js": "/chunks/settings-storage.bfb51d46fe9500c09f69.hot-update.js",
"/chunks/settings-subscription.bfb51d46fe9500c09f69.hot-update.js": "/chunks/settings-subscription.bfb51d46fe9500c09f69.hot-update.js",
"/chunks/user-create.bfb51d46fe9500c09f69.hot-update.js": "/chunks/user-create.bfb51d46fe9500c09f69.hot-update.js",
@@ -611,5 +611,23 @@
"/chunks/plan-settings.3a65ee9da570c90f6297.hot-update.js": "/chunks/plan-settings.3a65ee9da570c90f6297.hot-update.js",
"/chunks/plan-settings.dcff32efc576de996ee2.hot-update.js": "/chunks/plan-settings.dcff32efc576de996ee2.hot-update.js",
"/chunks/plan-create/metered.50f807a0d4bed310eb40.hot-update.js": "/chunks/plan-create/metered.50f807a0d4bed310eb40.hot-update.js",
"/chunks/plan-settings.9a034df6b5ef5adba55b.hot-update.js": "/chunks/plan-settings.9a034df6b5ef5adba55b.hot-update.js"
"/chunks/plan-settings.9a034df6b5ef5adba55b.hot-update.js": "/chunks/plan-settings.9a034df6b5ef5adba55b.hot-update.js",
"/chunks/plan-create/metered.4ef19a20e19e61a31348.hot-update.js": "/chunks/plan-create/metered.4ef19a20e19e61a31348.hot-update.js",
"/chunks/plan-create/metered.274a2e2a39cbc3e24a72.hot-update.js": "/chunks/plan-create/metered.274a2e2a39cbc3e24a72.hot-update.js",
"/chunks/plan-create/metered.c99a3c2c838cd5aa1252.hot-update.js": "/chunks/plan-create/metered.c99a3c2c838cd5aa1252.hot-update.js",
"/chunks/plan-settings.841a91b4b1caff6d3752.hot-update.js": "/chunks/plan-settings.841a91b4b1caff6d3752.hot-update.js",
"/chunks/plan-settings.e27dc14dc57c3b35f180.hot-update.js": "/chunks/plan-settings.e27dc14dc57c3b35f180.hot-update.js",
"/chunks/plan-settings.1288a4bc847c61c97b62.hot-update.js": "/chunks/plan-settings.1288a4bc847c61c97b62.hot-update.js",
"/chunks/plan-settings.dd51e493c7d1e5935096.hot-update.js": "/chunks/plan-settings.dd51e493c7d1e5935096.hot-update.js",
"/chunks/plan-settings.9e7d7d22a4787b317d00.hot-update.js": "/chunks/plan-settings.9e7d7d22a4787b317d00.hot-update.js",
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.471864fd47b6ed9bbe5a.hot-update.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.471864fd47b6ed9bbe5a.hot-update.js",
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.d291161f2184732e0c45.hot-update.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.d291161f2184732e0c45.hot-update.js",
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.bb83d799f88f9277821f.hot-update.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.bb83d799f88f9277821f.hot-update.js",
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.beb17b0ad621e24fa9c7.hot-update.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.beb17b0ad621e24fa9c7.hot-update.js",
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.1c63a4090e8c7c009c73.hot-update.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.1c63a4090e8c7c009c73.hot-update.js",
"/chunks/plans.3f36cf58fc7b3994de9e.hot-update.js": "/chunks/plans.3f36cf58fc7b3994de9e.hot-update.js",
"/chunks/plan-delete.d73a5950c032edd90159.hot-update.js": "/chunks/plan-delete.d73a5950c032edd90159.hot-update.js",
"/chunks/plan-create/metered.3eee477efc68c1983af0.hot-update.js": "/chunks/plan-create/metered.3eee477efc68c1983af0.hot-update.js",
"/chunks/settings-subscription.3eee477efc68c1983af0.hot-update.js": "/chunks/settings-subscription.3eee477efc68c1983af0.hot-update.js",
"/chunks/user-subscription.3eee477efc68c1983af0.hot-update.js": "/chunks/user-subscription.3eee477efc68c1983af0.hot-update.js"
}
@@ -82,7 +82,7 @@
<settings-icon v-if="['AppOthers', 'Profile', 'Password'].includes(result.action.value)" size="18" class="vue-feather text-theme"/>
<home-icon v-if="result.action.value === 'Files'" size="18" class="vue-feather text-theme"/>
<trash2-icon v-if="result.action.value === 'Trash'" size="18" class="vue-feather text-theme"/>
<database-icon v-if="result.action.value === 'CreateFixedPlan'" size="18" class="vue-feather text-theme"/>
<database-icon v-if="['CreateFixedPlan', 'CreateMeteredPlan'].includes(result.action.value)" size="18" class="vue-feather text-theme"/>
<user-plus-icon v-if="result.action.value === 'UserCreate'" size="18" class="vue-feather text-theme"/>
<users-icon v-if="['TeamFolders', 'Users'].includes(result.action.value)" size="18" class="vue-feather text-theme"/>
<user-check-icon v-if="result.action.value === 'SharedWithMe'" size="18" class="vue-feather text-theme"/>
@@ -90,7 +90,7 @@
<upload-cloud-icon v-if="result.action.value === 'RecentUploads'" size="18" class="vue-feather text-theme"/>
<file-text-icon v-if="['Invoices', 'Invoice'].includes(result.action.value)" size="18" class="vue-feather text-theme"/>
<database-icon v-if="result.action.value === 'Plans'" size="18" class="vue-feather text-theme"/>
<dollar-sign-icon v-if="['Subscriptions', 'Subscription'].includes(result.action.value)" size="18" class="vue-feather text-theme"/>
<dollar-sign-icon v-if="['Subscriptions', 'Subscription', 'MeteredSubscription'].includes(result.action.value)" size="18" class="vue-feather text-theme"/>
<globe-icon v-if="result.action.value === 'Language'" size="18" class="vue-feather text-theme"/>
<monitor-icon v-if="result.action.value === 'Pages'" size="18" class="vue-feather text-theme"/>
<box-icon v-if="result.action.value === 'Dashboard'" size="18" class="vue-feather text-theme"/>
@@ -255,6 +255,7 @@ export default {
computed: {
...mapGetters([
'isDarkMode',
'config',
'user',
]),
actionList() {
@@ -270,7 +271,9 @@ export default {
title: this.$t('Create Plan'),
action: {
type: 'route',
value: 'CreateFixedPlan',
value: this.config.subscriptionType === 'fixed'
? 'CreateFixedPlan'
: 'CreateMeteredPlan',
},
},
]
@@ -405,7 +408,9 @@ export default {
title: this.$t('Show my Subscription'),
action: {
type: 'route',
value: 'Subscription',
value: this.config.subscriptionType === 'fixed'
? 'Subscription'
: 'MeteredSubscription',
},
},
{
+8 -2
View File
@@ -37,10 +37,16 @@
</td>
<td>
<div class="flex space-x-2 w-full justify-end">
<router-link class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-green-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'PlanSettings', params: {id: row.data.id}}">
<router-link
:to="{name: 'PlanMeteredSettings', params: {id: row.data.id}}"
class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-green-100 dark:bg-2x-dark-foreground bg-light-background transition-colors"
>
<Edit2Icon size="15" class="opacity-75" />
</router-link>
<router-link class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-red-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'PlanDelete', params: {id: row.data.id}}">
<router-link
:to="{name: 'PlanMeteredDelete', params: {id: row.data.id}}"
class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-red-100 dark:bg-2x-dark-foreground bg-light-background transition-colors"
>
<Trash2Icon size="15" class="opacity-75" />
</router-link>
</div>
@@ -59,6 +59,19 @@
</AppInputText>
</ValidationProvider>
<!--Member-->
<div>
<AppInputSwitch :title="$t('Price per 1 Member')" :description="$t('Charge your user by the total members he use in his Team Folders.')">
<SwitchInput v-model="plan.features.member.active" class="switch" :state="plan.features.member.active" />
</AppInputSwitch>
</div>
<ValidationProvider v-if="plan.features.member.active" class="-mt-3" tag="div" mode="passive" name="Member Price" rules="required" v-slot="{ errors }">
<AppInputText class="w-full">
<input v-model="plan.features.member.per_unit" :placeholder="$t('Type the price per 1 member...')" type="number" step="0.01" min="0.01" max="999999999999" :class="{'border-red-700': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Flat Fee-->
<div>
<AppInputSwitch :title="$t('Flat Fee per Cycle')" :description="$t('Charge monthly flat fee.')" :is-last="! plan.features.flatFee.active">
@@ -143,6 +156,12 @@
first_unit: 1,
aggregate_strategy: 'maximum_usage',
},
member: {
active: false,
per_unit: undefined,
first_unit: 1,
aggregate_strategy: 'maximum_usage',
},
flatFee: {
active: false,
per_unit: undefined,
@@ -202,7 +221,7 @@
})
.catch(error => {
events.$emit('toaster', {
type: 'error',
type: 'danger',
message: this.$t('popup_error.title'),
})
})
@@ -76,7 +76,7 @@
})
.catch(() => {
events.$emit('toaster', {
type: 'success',
type: 'danger',
message: this.$t('popup_error.title'),
})
})
@@ -30,6 +30,11 @@
<input :value="formatCurrency(plan.attributes.currency, plan.attributes.features.storage.tiers[0].per_unit)" type="text" class="focus-border-theme input-dark" disabled/>
</AppInputText>
<!--Member-->
<AppInputText v-if="plan.attributes.features.member" :title="$t('Price per 1 Member')" :description="$t('Charge your user by the total members he use in his Team Folders.')" class="w-full">
<input :value="formatCurrency(plan.attributes.currency, plan.attributes.features.member.tiers[0].per_unit)" type="text" class="focus-border-theme input-dark" disabled/>
</AppInputText>
<!--Flat Fee-->
<AppInputText v-if="plan.attributes.features.flatFee" :title="$t('Flat Fee per Cycle')" :description="$t('Charge monthly flat fee.')" class="w-full">
<input :value="formatCurrency(plan.attributes.currency, plan.attributes.features.flatFee.tiers[0].per_unit)" type="text" class="focus-border-theme input-dark" disabled/>
@@ -70,6 +75,7 @@
},
methods: {
formatCurrency(currency, amount) {
// TODO: add user locale
let formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
@@ -133,7 +133,7 @@
})
.catch(() => {
events.$emit('toaster', {
type: 'success',
type: 'danger',
message: this.$t('popup_error.title'),
})
})
@@ -287,7 +287,7 @@
})
.catch(() => {
events.$emit('toaster', {
type: 'success',
type: 'danger',
message: this.$t('popup_error.title'),
})
})
@@ -318,7 +318,7 @@
})
.catch(() => {
events.$emit('toaster', {
type: 'success',
type: 'danger',
message: this.$t('popup_error.title'),
})
})
@@ -12,15 +12,15 @@ class FormatUsageEstimatesAction
return $usage->mapWithKeys(function ($estimate) use ($currency) {
// Format usage
$usage = match ($estimate['feature']) {
'bandwidth' => Metric::megabytes($estimate['usage'])->format(),
'storage' => Metric::megabytes($estimate['usage'])->format(),
'bandwidth', 'storage' => Metric::megabytes($estimate['usage'])->format(),
'flatFee' => intval($estimate['usage']) . ' ' . __('Pcs.'),
'member' => intval($estimate['usage']) . ' ' . __('Mem.'),
};
// Normalize units
$amount = match ($estimate['feature']) {
'bandwidth', 'storage' => $estimate['amount'] / 1000,
'flatFee' => $estimate['amount'],
'flatFee', 'member' => $estimate['amount'],
};
return [
@@ -1,6 +1,7 @@
<?php
namespace Support\Scheduler\Actions;
use App\Users\Models\User;
use DB;
use VueFileManager\Subscription\Domain\Subscriptions\Models\Subscription;
@@ -23,6 +24,10 @@ class ReportUsageAction
if ($subscription->plan->meteredFeatures()->where('key', 'flatFee')->exists()) {
$this->recordFlatFee($subscription);
}
if ($subscription->plan->meteredFeatures()->where('key', 'member')->exists()) {
$this->recordMemberUsage($subscription);
}
});
}
@@ -60,4 +65,35 @@ class ReportUsageAction
// Record flat fee
$subscription->recordUsage('flatFee', 1);
}
// TODO: Refactor this function to get total used team members, same function here UserLimitation.php@getMaxTeamMembers
private function recordMemberUsage(Subscription $subscription): void
{
$userTeamFolderIds = DB::table('team_folder_members')
->where('user_id', $subscription->user_id)
->pluck('parent_id');
$memberIds = DB::table('team_folder_members')
->where('user_id', '!=', $subscription->user_id)
->whereIn('parent_id', $userTeamFolderIds)
->pluck('user_id')
->unique();
// Get member emails
$memberEmails = User::whereIn('id', $memberIds)
->pluck('email');
// Get active invitation emails
$InvitationEmails = DB::table('team_folder_invitations')
->where('status', 'pending')
->where('inviter_id', $subscription->user_id)
->pluck('email')
->unique();
// Get allowed emails in the limit
$totalUsedEmails = $memberEmails->merge($InvitationEmails)
->unique();
$subscription->recordUsage('member', $totalUsedEmails->count());
}
}
+10
View File
@@ -120,6 +120,10 @@ class UserSubscriptionTest extends TestCase
'feature' => 'flatFee',
'amount' => 2.49,
'usage' => 1,
], [
'feature' => 'member',
'amount' => 0.20,
'usage' => 2,
],
]);
@@ -146,6 +150,12 @@ class UserSubscriptionTest extends TestCase
'cost' => '$2.49',
'usage' => '1 Pcs.',
],
'member' => [
'feature' => 'member',
'amount' => 0.20,
'cost' => '$0.20',
'usage' => '2 Mem.',
],
];
$this->assertEquals($expected, $estimates);
+2 -1
View File
@@ -32,11 +32,12 @@ class SchedulerTest extends TestCase
]);
PlanMeteredFeature::factory()
->count(3)
->count(4)
->sequence(
['key' => 'storage'],
['key' => 'bandwidth'],
['key' => 'flatFee'],
['key' => 'member'],
)
->create([
'plan_id' => $plan->id,