Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117986616
D2446.1775528144.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
21 KB
Referenced Files
None
Subscribers
None
D2446.1775528144.diff
View Options
diff --git a/src/app/Console/Commands/DomainSetWallet.php b/src/app/Console/Commands/DomainSetWallet.php
--- a/src/app/Console/Commands/DomainSetWallet.php
+++ b/src/app/Console/Commands/DomainSetWallet.php
@@ -60,6 +60,7 @@
'wallet_id' => $wallet->id,
'sku_id' => $sku->id,
'cost' => 0,
+ 'fee' => 0,
'entitleable_id' => $domain->id,
'entitleable_type' => Domain::class,
]
diff --git a/src/app/Domain.php b/src/app/Domain.php
--- a/src/app/Domain.php
+++ b/src/app/Domain.php
@@ -89,6 +89,7 @@
'wallet_id' => $wallet_id,
'sku_id' => $sku->id,
'cost' => $sku->pivot->cost(),
+ 'fee' => $sku->fee,
'entitleable_id' => $this->id,
'entitleable_type' => Domain::class
]
diff --git a/src/app/Entitlement.php b/src/app/Entitlement.php
--- a/src/app/Entitlement.php
+++ b/src/app/Entitlement.php
@@ -11,10 +11,18 @@
*
* Owned by a {@link \App\User}, billed to a {@link \App\Wallet}.
*
- * @property \App\User $owner The owner of this entitlement (subject).
- * @property \App\Sku $sku The SKU to which this entitlement applies.
- * @property \App\Wallet $wallet The wallet to which this entitlement is charged.
- * @property \App\Domain|\App\User $entitleable The entitled object (receiver of the entitlement).
+ * @property int $cost
+ * @property ?string $description
+ * @property \App\Domain|\App\User $entitleable The entitled object (receiver of the entitlement).
+ * @property int $entitleable_id
+ * @property string $entitleable_type
+ * @property int $fee
+ * @property string $id
+ * @property \App\User $owner The owner of this entitlement (subject).
+ * @property \App\Sku $sku The SKU to which this entitlement applies.
+ * @property string $sku_id
+ * @property \App\Wallet $wallet The wallet to which this entitlement is charged.
+ * @property string $wallet_id
*/
class Entitlement extends Model
{
@@ -45,11 +53,13 @@
'entitleable_id',
'entitleable_type',
'cost',
- 'description'
+ 'description',
+ 'fee',
];
protected $casts = [
'cost' => 'integer',
+ 'fee' => 'integer'
];
/**
diff --git a/src/app/Group.php b/src/app/Group.php
--- a/src/app/Group.php
+++ b/src/app/Group.php
@@ -64,6 +64,7 @@
'wallet_id' => $wallet->id,
'sku_id' => $sku->id,
'cost' => $exists >= $sku->units_free ? $sku->cost : 0,
+ 'fee' => $exists >= $sku->units_free ? $sku->fee : 0,
'entitleable_id' => $this->id,
'entitleable_type' => Group::class
]);
diff --git a/src/app/Observers/EntitlementObserver.php b/src/app/Observers/EntitlementObserver.php
--- a/src/app/Observers/EntitlementObserver.php
+++ b/src/app/Observers/EntitlementObserver.php
@@ -123,6 +123,7 @@
return;
}
+ $fee = 0;
$cost = 0;
$now = Carbon::now();
@@ -132,6 +133,7 @@
// just in case this had not been billed yet, ever
$diffInMonths = $entitlement->updated_at->diffInMonths($now);
$cost += (int) ($entitlement->cost * $discount * $diffInMonths);
+ $fee += (int) ($entitlement->fee * $diffInMonths);
// this moves the hypothetical updated at forward to however many months past the original
$updatedAt = $entitlement->updated_at->copy()->addMonthsWithoutOverflow($diffInMonths);
@@ -153,8 +155,18 @@
}
$pricePerDay = $entitlement->cost / $daysInMonth;
+ $feePerDay = $entitlement->fee / $daysInMonth;
$cost += (int) (round($pricePerDay * $discount * $diffInDays, 0));
+ $fee += (int) (round($feePerDay * $diffInDays, 0));
+
+ $profit = $cost - $fee;
+
+ if ($profit != 0 && $owner->tenant && ($wallet = $owner->tenant->wallet()) {
+ $desc = "Charged user {$this->owner->email}";
+ $method = $profit > 0 ? 'credit' : 'debit';
+ $wallet->{$method}(abs($profit), $desc);
+ }
if ($cost == 0) {
return;
diff --git a/src/app/Observers/PackageObserver.php b/src/app/Observers/PackageObserver.php
--- a/src/app/Observers/PackageObserver.php
+++ b/src/app/Observers/PackageObserver.php
@@ -27,5 +27,7 @@
break;
}
}
+
+ $package->tenant_id = \config('app.tenant_id');
}
}
diff --git a/src/app/Observers/PackageSkuObserver.php b/src/app/Observers/PackageSkuObserver.php
--- a/src/app/Observers/PackageSkuObserver.php
+++ b/src/app/Observers/PackageSkuObserver.php
@@ -6,6 +6,25 @@
class PackageSkuObserver
{
+ /**
+ * Handle the "creating" event on an PackageSku relation.
+ *
+ * Ensures that the entries belong to the same tenant.
+ *
+ * @param \App\PackageSku $packageSku The package-sku relation
+ *
+ * @return void
+ */
+ public function creating(PackageSku $packageSku)
+ {
+ $package = $packageSku->package;
+ $sku = $packageSku->sku;
+
+ if ($package->tenant_id != $sku->tenant_id) {
+ throw new \Exception("Package and SKU owned by different tenants");
+ }
+ }
+
/**
* Handle the "created" event on an PackageSku relation
*
diff --git a/src/app/Observers/PlanObserver.php b/src/app/Observers/PlanObserver.php
--- a/src/app/Observers/PlanObserver.php
+++ b/src/app/Observers/PlanObserver.php
@@ -27,5 +27,7 @@
break;
}
}
+
+ $plan->tenant_id = \config('app.tenant_id');
}
}
diff --git a/src/app/Observers/PlanPackageObserver.php b/src/app/Observers/PlanPackageObserver.php
new file mode 100644
--- /dev/null
+++ b/src/app/Observers/PlanPackageObserver.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Observers;
+
+use App\PlanPackage;
+
+class PlanPackageObserver
+{
+ /**
+ * Handle the "creating" event on an PlanPackage relation.
+ *
+ * Ensures that the entries belong to the same tenant.
+ *
+ * @param \App\PlanPackage $planPackage The plan-package relation
+ *
+ * @return void
+ */
+ public function creating(PlanPackage $planPackage)
+ {
+ $package = $planPackage->package;
+ $plan = $planPackage->plan;
+
+ if ($package->tenant_id != $plan->tenant_id) {
+ throw new \Exception("Package and Plan owned by different tenants");
+ }
+ }
+}
diff --git a/src/app/Observers/SkuObserver.php b/src/app/Observers/SkuObserver.php
--- a/src/app/Observers/SkuObserver.php
+++ b/src/app/Observers/SkuObserver.php
@@ -22,5 +22,9 @@
break;
}
}
+
+ $sku->tenant_id = \config('app.tenant_id');
+
+ // TODO: We should make sure that tenant_id + title is unique
}
}
diff --git a/src/app/Package.php b/src/app/Package.php
--- a/src/app/Package.php
+++ b/src/app/Package.php
@@ -21,6 +21,13 @@
* * Free package: mailbox + quota.
*
* Selecting a package will therefore create a set of entitlments from SKUs.
+ *
+ * @property string $description
+ * @property int $discount_rate
+ * @property string $id
+ * @property string $name
+ * @property ?int $tenant_id
+ * @property string $title
*/
class Package extends Model
{
@@ -69,7 +76,10 @@
return $costs;
}
- public function isDomain()
+ /**
+ * Checks whether the package contains a domain SKU.
+ */
+ public function isDomain(): bool
{
foreach ($this->skus as $sku) {
if ($sku->handler_class::entitleableClass() == \App\Domain::class) {
@@ -94,4 +104,14 @@
['qty']
);
}
+
+ /**
+ * The tenant for this package.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function tenant()
+ {
+ return $this->belongsTo('App\Tenant', 'tenant_id', 'id');
+ }
}
diff --git a/src/app/Plan.php b/src/app/Plan.php
--- a/src/app/Plan.php
+++ b/src/app/Plan.php
@@ -13,7 +13,16 @@
* A "Family Plan" as such may exist of "2 or more Kolab packages",
* and apply a discount for the third and further Kolab packages.
*
+ * @property string $description
+ * @property int $discount_qty
+ * @property int $discount_rate
+ * @property string $id
+ * @property string $name
* @property \App\Package[] $packages
+ * @property datetime $promo_from
+ * @property datetime $promo_to
+ * @property ?int $tenant_id
+ * @property string $title
*/
class Plan extends Model
{
@@ -105,4 +114,14 @@
return false;
}
+
+ /**
+ * The tenant for this plan.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function tenant()
+ {
+ return $this->belongsTo('App\Tenant', 'tenant_id', 'id');
+ }
}
diff --git a/src/app/PlanPackage.php b/src/app/PlanPackage.php
--- a/src/app/PlanPackage.php
+++ b/src/app/PlanPackage.php
@@ -15,6 +15,7 @@
* @property int $qty_max
* @property int $qty_min
* @property \App\Package $package
+ * @property \App\Plan $plan
*/
class PlanPackage extends Pivot
{
@@ -54,8 +55,23 @@
return $costs;
}
+ /**
+ * The package in this relation.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
public function package()
{
return $this->belongsTo('App\Package');
}
+
+ /**
+ * The plan in this relation.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function plan()
+ {
+ return $this->belongsTo('App\Plan');
+ }
}
diff --git a/src/app/Providers/AppServiceProvider.php b/src/app/Providers/AppServiceProvider.php
--- a/src/app/Providers/AppServiceProvider.php
+++ b/src/app/Providers/AppServiceProvider.php
@@ -35,6 +35,7 @@
\App\Package::observe(\App\Observers\PackageObserver::class);
\App\PackageSku::observe(\App\Observers\PackageSkuObserver::class);
\App\Plan::observe(\App\Observers\PlanObserver::class);
+ \App\PlanPackage::observe(\App\Observers\PlanPackageObserver::class);
\App\SignupCode::observe(\App\Observers\SignupCodeObserver::class);
\App\SignupInvitation::observe(\App\Observers\SignupInvitationObserver::class);
\App\Sku::observe(\App\Observers\SkuObserver::class);
diff --git a/src/app/Providers/PaymentProvider.php b/src/app/Providers/PaymentProvider.php
--- a/src/app/Providers/PaymentProvider.php
+++ b/src/app/Providers/PaymentProvider.php
@@ -261,6 +261,8 @@
$refund['status'] = self::STATUS_PAID;
$refund['amount'] = -1 * $amount;
+ // FIXME: Refunds/chargebacks are out of the reseller comissioning for now
+
$this->storePayment($refund, $wallet->id);
}
diff --git a/src/app/Sku.php b/src/app/Sku.php
--- a/src/app/Sku.php
+++ b/src/app/Sku.php
@@ -7,6 +7,18 @@
/**
* The eloquent definition of a Stock Keeping Unit (SKU).
+ *
+ * @property bool $active
+ * @property int $cost
+ * @property string $description
+ * @property int $fee The fee that the tenant pays to us
+ * @property string $handler_class
+ * @property string $id
+ * @property string $name
+ * @property string $period
+ * @property ?int $tenant_id
+ * @property string $title
+ * @property int $units_free
*/
class Sku extends Model
{
@@ -23,6 +35,7 @@
'active',
'cost',
'description',
+ 'fee',
'handler_class',
'name',
// persist for annual domain registration
@@ -59,4 +72,14 @@
'package_skus'
)->using('App\PackageSku')->withPivot(['cost', 'qty']);
}
+
+ /**
+ * The tenant for this SKU.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function tenant()
+ {
+ return $this->belongsTo('App\Tenant', 'tenant_id', 'id');
+ }
}
diff --git a/src/app/Tenant.php b/src/app/Tenant.php
--- a/src/app/Tenant.php
+++ b/src/app/Tenant.php
@@ -37,4 +37,16 @@
{
return $this->hasMany('App\SignupInvitation');
}
+
+ /*
+ * Returns the wallet of the tanant (reseller's wallet).
+ *
+ * @return ?\App\Wallet A wallet object
+ */
+ public function wallet(): ?Wallet
+ {
+ \App\User::where('role', 'reseller')->where('tenant_id', $this->id)->first();
+
+ return $user ? $user->wallets->first() : null;
+ }
}
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -126,6 +126,7 @@
'wallet_id' => $wallet_id,
'sku_id' => $sku->id,
'cost' => $sku->pivot->cost(),
+ 'fee' => $sku->fee,
'entitleable_id' => $user->id,
'entitleable_type' => User::class
]
@@ -179,6 +180,7 @@
'wallet_id' => $wallet->id,
'sku_id' => $sku->id,
'cost' => $exists >= $sku->units_free ? $sku->cost : 0,
+ 'fee' => $exists >= $sku->units_free ? $sku->fee : 0,
'entitleable_id' => $this->id,
'entitleable_type' => User::class
]);
diff --git a/src/app/Wallet.php b/src/app/Wallet.php
--- a/src/app/Wallet.php
+++ b/src/app/Wallet.php
@@ -76,6 +76,7 @@
return 0;
}
+ $profit = 0;
$charges = 0;
$discount = $this->getDiscountRate();
@@ -101,8 +102,10 @@
$diff = $entitlement->updated_at->diffInMonths(Carbon::now());
$cost = (int) ($entitlement->cost * $discount * $diff);
+ $fee = (int) ($entitlement->fee * $diff);
$charges += $cost;
+ $profit += $cost - $fee;
// if we're in dry-run, you know...
if (!$apply) {
@@ -127,6 +130,16 @@
if ($apply) {
$this->debit($charges, $entitlementTransactions);
+
+ // Credit/debit the reseller
+ if ($profit != 0 && $this->owner->tenant) {
+ // FIXME: Should we have a simpler way to skip this for non-reseller tenant(s)
+ if ($wallet = $this->owner->tenant->wallet()) {
+ $desc = "Charged user {$this->owner->email}";
+ $method = $profit > 0 ? 'credit' : 'debit';
+ $wallet->{$method}(abs($profit), $desc);
+ }
+ }
}
DB::commit();
diff --git a/src/database/migrations/2020_10_29_100000_add_beta_skus.php b/src/database/migrations/2020_10_29_100000_add_beta_skus.php
--- a/src/database/migrations/2020_10_29_100000_add_beta_skus.php
+++ b/src/database/migrations/2020_10_29_100000_add_beta_skus.php
@@ -14,31 +14,7 @@
*/
public function up()
{
- if (!\App\Sku::where('title', 'beta')->first()) {
- \App\Sku::create([
- 'title' => 'beta',
- 'name' => 'Beta program',
- 'description' => 'Access to beta program subscriptions',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta',
- 'active' => false,
- ]);
- }
-
- if (!\App\Sku::where('title', 'meet')->first()) {
- \App\Sku::create([
- 'title' => 'meet',
- 'name' => 'Video chat',
- 'description' => 'Video conferencing tool',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Beta\Meet',
- 'active' => true,
- ]);
- }
+ // empty
}
/**
diff --git a/src/database/migrations/2020_12_28_140000_create_groups_table.php b/src/database/migrations/2020_12_28_140000_create_groups_table.php
--- a/src/database/migrations/2020_12_28_140000_create_groups_table.php
+++ b/src/database/migrations/2020_12_28_140000_create_groups_table.php
@@ -28,19 +28,6 @@
$table->primary('id');
}
);
-
- if (!\App\Sku::where('title', 'group')->first()) {
- \App\Sku::create([
- 'title' => 'group',
- 'name' => 'Group',
- 'description' => 'Distribution list',
- 'cost' => 0,
- 'units_free' => 0,
- 'period' => 'monthly',
- 'handler_class' => 'App\Handlers\Group',
- 'active' => true,
- ]);
- }
}
/**
diff --git a/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php b/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php
--- a/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php
+++ b/src/database/migrations/2021_01_26_150000_change_sku_descriptions.php
@@ -15,14 +15,20 @@
public function up()
{
$beta_sku = \App\Sku::where('title', 'beta')->first();
- $beta_sku->name = 'Private Beta (invitation only)';
- $beta_sku->description = 'Access to the private beta program subscriptions';
- $beta_sku->save();
+
+ if ($beta_sku) {
+ $beta_sku->name = 'Private Beta (invitation only)';
+ $beta_sku->description = 'Access to the private beta program subscriptions';
+ $beta_sku->save();
+ }
$meet_sku = \App\Sku::where('title', 'meet')->first();
- $meet_sku->name = 'Voice & Video Conferencing (public beta)';
- $meet_sku->handler_class = 'App\Handlers\Meet';
- $meet_sku->save();
+
+ if ($meet_sku) {
+ $meet_sku->name = 'Voice & Video Conferencing (public beta)';
+ $meet_sku->handler_class = 'App\Handlers\Meet';
+ $meet_sku->save();
+ }
}
/**
diff --git a/src/database/migrations/2021_02_19_093832_discounts_add_tenant_id.php b/src/database/migrations/2021_02_19_093832_discounts_add_tenant_id.php
--- a/src/database/migrations/2021_02_19_093832_discounts_add_tenant_id.php
+++ b/src/database/migrations/2021_02_19_093832_discounts_add_tenant_id.php
@@ -17,7 +17,7 @@
Schema::table(
'discounts',
function (Blueprint $table) {
- $table->bigInteger('tenant_id')->unsigned()->default(\config('app.tenant_id'))->nullable();
+ $table->bigInteger('tenant_id')->unsigned()->nullable();
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('set null');
}
diff --git a/src/database/migrations/2021_02_19_093833_domains_add_tenant_id.php b/src/database/migrations/2021_02_19_093833_domains_add_tenant_id.php
--- a/src/database/migrations/2021_02_19_093833_domains_add_tenant_id.php
+++ b/src/database/migrations/2021_02_19_093833_domains_add_tenant_id.php
@@ -17,7 +17,7 @@
Schema::table(
'domains',
function (Blueprint $table) {
- $table->bigInteger('tenant_id')->unsigned()->default(\config('app.tenant_id'))->nullable();
+ $table->bigInteger('tenant_id')->unsigned()->nullable();
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('set null');
}
diff --git a/src/database/migrations/2021_04_09_100000_tenant_comissioning.php b/src/database/migrations/2021_04_09_100000_tenant_comissioning.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2021_04_09_100000_tenant_comissioning.php
@@ -0,0 +1,71 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Schema;
+
+// phpcs:ignore
+class TenantComissioning extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ // Add tenant_id columns
+ foreach (['plans', 'packages', 'skus'] as $table) {
+ Schema::table(
+ $table,
+ function (Blueprint $table) {
+ $table->bigInteger('tenant_id')->unsigned()->nullable();
+
+ $table->foreign('tenant_id')->references('id')->on('tenants')
+ ->onDelete('cascade')->onUpdate('cascade');
+ }
+ );
+ }
+
+ // Add fee column
+ foreach (['entitlements', 'skus'] as $table) {
+ Schema::table(
+ $table,
+ function (Blueprint $table) {
+ $table->integer('fee')->nullable();
+ }
+ );
+ }
+
+ // FIXME: Should we also have package_skus.fee ?
+ // We have package_skus.cost, but I think it is not used anywhere.
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ foreach (['plans', 'packages', 'skus'] as $table) {
+ Schema::table(
+ $table,
+ function (Blueprint $table) {
+ $table->dropForeign(['tenant_id']);
+ $table->dropColumn('tenant_id');
+ }
+ );
+ }
+
+ foreach (['entitlements', 'skus'] as $table) {
+ Schema::table(
+ $table,
+ function (Blueprint $table) {
+ $table->dropColumn('fee');
+ }
+ );
+ }
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Apr 7, 2:15 AM (4 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18840468
Default Alt Text
D2446.1775528144.diff (21 KB)
Attached To
Mode
D2446: Reseller comissioning system
Attached
Detach File
Event Timeline