Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions bundle/src/Comms/Event/ToCore/Billing/RecordMeteredUsage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Hyvor\Internal\Bundle\Comms\Event\ToCore\Billing;

use Hyvor\Internal\Bundle\Comms\Event\AbstractEvent;
use Hyvor\Internal\Component\Component;

class RecordMeteredUsage extends AbstractEvent
{
public function __construct(
private Component $component,
private int $organizationId,
private int $amount,
private string $idempotencyKey,
) {}

public function getComponent(): Component
{
return $this->component;
}

public function getOrganizationId(): int
{
return $this->organizationId;
}

public function getAmount(): int
{
return $this->amount;
}

public function getIdempotencyKey(): string
{
return $this->idempotencyKey;
}

public function from(): array
{
return [];
}

public function to(): array
{
return [Component::CORE];
}
}
25 changes: 23 additions & 2 deletions src/Billing/Billing.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Hyvor\Internal\Component\Component;
use Hyvor\Internal\Component\InstanceUrlResolver;
use Hyvor\Internal\InternalConfig;
use Hyvor\Internal\Bundle\Comms\Event\ToCore\Billing\RecordMeteredUsage;

class Billing implements BillingInterface
{
Expand All @@ -17,8 +18,7 @@ public function __construct(
private InternalConfig $internalConfig,
private InstanceUrlResolver $instanceUrlResolver,
private CommsInterface $comms,
) {
}
) {}

/**
* @return array{urlNew: string, urlChange: string}
Expand Down Expand Up @@ -95,4 +95,25 @@ public function licenses(array $organizationIds, ?Component $component = null):
return $response->getLicenses();
}

/**
* @throws CommsApiFailedException
*/
public function recordMeteredUsage(
int $organizationId,
int $amount,
string $idempotencyKey,
?Component $component = null
): void {
$component ??= $this->internalConfig->getComponent();

$this->comms->send(
new RecordMeteredUsage(
$component,
$organizationId,
$amount,
$idempotencyKey
),
Component::CORE
);
}
}
23 changes: 20 additions & 3 deletions src/Billing/BillingFake.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class BillingFake implements BillingInterface
* @param array<int, ResolvedLicense>|(callable(int[] $organizationIds, Component $component) : array<int, ResolvedLicense>) $licenses
* @return void
*/
public static function enable(array|callable $licenses): void {
public static function enable(array|callable $licenses): void
{
app()->singleton(Billing::class, function () use ($licenses) {
return new BillingFake(
app(InternalConfig::class),
Expand Down Expand Up @@ -48,8 +49,7 @@ public function __construct(
* @param array<int, ResolvedLicense>|(callable(int[] $organizationIds, Component $component) : array<int, ResolvedLicense>)|null $licenses
*/
private readonly mixed $licenses = null
) {
}
) {}

public function license(int $organizationId, ?Component $component = null): ResolvedLicense
{
Expand Down Expand Up @@ -83,4 +83,21 @@ public function licenses(array $organizationIds, ?Component $component = null):
return ($this->licenses)($organizationIds, $component);
}

public array $recordedUsage = [];

public function recordMeteredUsage(
int $organizationId,
int $amount,
string $idempotencyKey,
?Component $component = null
): void {
$component ??= $this->internalConfig->getComponent();

$this->recordedUsage[] = [
'organizationId' => $organizationId,
'amount' => $amount,
'idempotencyKey' => $idempotencyKey,
'component' => $component,
];
}
}
12 changes: 12 additions & 0 deletions src/Billing/BillingInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,16 @@ public function license(int $organizationId, ?Component $component = null): Reso
* @throws CommsApiFailedException
*/
public function licenses(array $organizationIds, ?Component $component = null): array;

/**
* @param string $idempotencyKey A unique key within the component to ensure the same usage isn't recorded twice.
* ex: orgId + date
* @throws CommsApiFailedException
*/
public function recordMeteredUsage(
int $organizationId,
int $amount,
string $idempotencyKey,
?Component $component = null
): void;
}
16 changes: 16 additions & 0 deletions src/Billing/License/FortguardLicense.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Hyvor\Internal\Billing\License;

final class FortguardLicense extends License
{

public function __construct(
public int $credits
) {}

public static function trial(): static
{
return new self(credits: 2000);
}
}
42 changes: 42 additions & 0 deletions src/Billing/License/Plan/FortguardPlan.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Hyvor\Internal\Billing\License\Plan;

use Hyvor\Internal\Billing\License\FortguardLicense;

// 20k
// 50k - 40k

// 1st - 50k

// 50k 50€
// 20k - 40k

/**
* @extends PlanAbstract<FortguardLicense>
*/
class FortguardPlan extends PlanAbstract
{

protected function config(): void
{
// Meters
$this->meter(
name: 'base_credits',
nameReadable: 'Extra Credits',
property: 'credits',
pricePerUnit: 0.0004, // 4 per 10,000
);

// Version 1
$this->version(1, function () {
$this->plan(
'base',
30,
new FortguardLicense(credits: 150_000),
nameReadable: 'Base',
meterName: 'base_credits',
);
});
}
}
13 changes: 13 additions & 0 deletions src/Billing/License/Plan/Meter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Hyvor\Internal\Billing\License\Plan;

class Meter
{
public function __construct(
public string $name,
public string $property,
public string $nameReadable,
public float $pricePerUnit,
) {}
}
6 changes: 5 additions & 1 deletion src/Billing/License/Plan/Plan.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ public function __construct(
* Usually, if one plan has a group, all plans should have a group
*/
public ?string $group = null,

/**
* Meter for usage-based billing
*/
public ?Meter $meter = null,
) {
$this->nameReadable = $nameReadable ?? ucfirst($this->name);
}

}
38 changes: 38 additions & 0 deletions src/Billing/License/Plan/PlanAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ abstract class PlanAbstract
*/
private array $versions;

/**
* @var array<string, Meter>
*/
private array $meters = [];

// this only helps with config() method. Do not use for anything else.
private ?int $currentVersionForConfig = null;

Expand Down Expand Up @@ -48,15 +53,23 @@ protected function plan(
License $license,
?string $nameReadable = null,
?string $group = null,
?string $meterName = null,
): void {
assert($this->currentVersionForConfig !== null);

$meter = $meterName ? $this->meters[$meterName] : null;
if ($meter) {
assert(property_exists($license, $meter->property));
}

$plan = new Plan(
$this->currentVersionForConfig,
$name,
$monthlyPrice,
$license,
$nameReadable,
$group,
$meter,
);

$currentVersionPlans = $this->versions[$this->currentVersionForConfig];
Expand All @@ -65,6 +78,24 @@ protected function plan(
$this->versions[$this->currentVersionForConfig] = $currentVersionPlans;
}

protected function meter(
string $name,
string $nameReadable,
string $property,
float $pricePerUnit,
): void {
if (isset($this->meters[$name])) {
throw new \Exception("Meter with name $name already exists");
}

$this->meters[$name] = new Meter(
name: $name,
property: $property,
nameReadable: $nameReadable,
pricePerUnit: $pricePerUnit,
);
}

/**
* @return array<int, array<string, Plan<T>>>
*/
Expand Down Expand Up @@ -107,4 +138,11 @@ public function tryGetPlan(string $name, ?int $version = null): ?Plan
return $this->versions[$version][$name] ?? null;
}

/**
* @return array<string, Meter>
*/
public function getMeters(): array
{
return $this->meters;
}
}
Loading