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 6709d83..e6d9f78 100644 --- a/src/Billing/BillingFake.php +++ b/src/Billing/BillingFake.php @@ -15,7 +15,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), @@ -87,6 +88,26 @@ public function licenses(array $organizationIds, ?Component $component = null): return ($this->licenses)($organizationIds, $component); } + /** + * @var array + */ + 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, + ]; + } /** * @param array|(callable(int[] $organizationIds, Component $component) : array) $licenses */ 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/Plan/FortGuardPlan.php b/src/Billing/License/Plan/FortGuardPlan.php index 490f7f8..a3b7905 100644 --- a/src/Billing/License/Plan/FortGuardPlan.php +++ b/src/Billing/License/Plan/FortGuardPlan.php @@ -11,6 +11,14 @@ class FortGuardPlan extends PlanAbstract { protected function config(): void { + // Meters + $this->meter( + name: 'base_credits', + nameReadable: 'Additional Usage (Base Plan)', + property: 'credits', + pricePerUnit: 0.0004, // 4 per 10,000 + ); + // Version 1 $this->version(1, function () { $this->plan( @@ -21,6 +29,7 @@ protected function config(): void ), nameReadable: 'Base Plan', monthlyOnly: true, + 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 62bffbd..3bd6452 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; @@ -50,8 +55,15 @@ protected function plan( ?string $group = null, bool $annualOnly = false, bool $monthlyOnly = false, + ?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, @@ -61,6 +73,7 @@ protected function plan( $group, $annualOnly, $monthlyOnly, + $meter, ); $currentVersionPlans = $this->versions[$this->currentVersionForConfig]; @@ -69,6 +82,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>> */ @@ -110,4 +141,12 @@ public function tryGetPlan(string $name, ?int $version = null): ?Plan $version ??= $this->getCurrentVersion(); return $this->versions[$version][$name] ?? null; } + + /** + * @return array + */ + public function getMeters(): array + { + return $this->meters; + } }