diff --git a/bundle/src/Comms/Event/ToCore/Billing/RecordMeteredUsage.php b/bundle/src/Comms/Event/ToCore/Billing/RecordMeteredUsage.php new file mode 100644 index 0000000..212c393 --- /dev/null +++ b/bundle/src/Comms/Event/ToCore/Billing/RecordMeteredUsage.php @@ -0,0 +1,46 @@ +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]; + } +} diff --git a/src/Billing/Billing.php b/src/Billing/Billing.php index f5b1f20..813fa42 100644 --- a/src/Billing/Billing.php +++ b/src/Billing/Billing.php @@ -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 { @@ -17,8 +18,7 @@ public function __construct( private InternalConfig $internalConfig, private InstanceUrlResolver $instanceUrlResolver, private CommsInterface $comms, - ) { - } + ) {} /** * @return array{urlNew: string, urlChange: string} @@ -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 + ); + } } diff --git a/src/Billing/BillingFake.php b/src/Billing/BillingFake.php index 8d88ac8..a1db4cd 100644 --- a/src/Billing/BillingFake.php +++ b/src/Billing/BillingFake.php @@ -14,7 +14,8 @@ class BillingFake implements BillingInterface * @param array|(callable(int[] $organizationIds, Component $component) : array) $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), @@ -48,8 +49,7 @@ public function __construct( * @param array|(callable(int[] $organizationIds, Component $component) : array)|null $licenses */ private readonly mixed $licenses = null - ) { - } + ) {} public function license(int $organizationId, ?Component $component = null): ResolvedLicense { @@ -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, + ]; + } } diff --git a/src/Billing/BillingInterface.php b/src/Billing/BillingInterface.php index 15efc2b..bb30c0f 100644 --- a/src/Billing/BillingInterface.php +++ b/src/Billing/BillingInterface.php @@ -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; } diff --git a/src/Billing/License/FortguardLicense.php b/src/Billing/License/FortguardLicense.php new file mode 100644 index 0000000..6bfc066 --- /dev/null +++ b/src/Billing/License/FortguardLicense.php @@ -0,0 +1,16 @@ + + */ +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', + ); + }); + } +} diff --git a/src/Billing/License/Plan/Meter.php b/src/Billing/License/Plan/Meter.php new file mode 100644 index 0000000..982cb1a --- /dev/null +++ b/src/Billing/License/Plan/Meter.php @@ -0,0 +1,13 @@ +nameReadable = $nameReadable ?? ucfirst($this->name); } - } diff --git a/src/Billing/License/Plan/PlanAbstract.php b/src/Billing/License/Plan/PlanAbstract.php index ea796fb..0a145b1 100644 --- a/src/Billing/License/Plan/PlanAbstract.php +++ b/src/Billing/License/Plan/PlanAbstract.php @@ -15,6 +15,11 @@ abstract class PlanAbstract */ private array $versions; + /** + * @var array + */ + private array $meters = []; + // this only helps with config() method. Do not use for anything else. private ?int $currentVersionForConfig = null; @@ -48,8 +53,15 @@ 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, @@ -57,6 +69,7 @@ protected function plan( $license, $nameReadable, $group, + $meter, ); $currentVersionPlans = $this->versions[$this->currentVersionForConfig]; @@ -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>> */ @@ -107,4 +138,11 @@ public function tryGetPlan(string $name, ?int $version = null): ?Plan return $this->versions[$version][$name] ?? null; } + /** + * @return array + */ + public function getMeters(): array + { + return $this->meters; + } }