finished setup

This commit is contained in:
John Yeo
2026-02-18 19:17:31 +00:00
parent 1aecdaced2
commit cd524a641f
46 changed files with 449 additions and 414 deletions

View File

@@ -13,7 +13,9 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx
ID which you provided when creating the customer
</DynamicParamField>
<DynamicParamField body="feature_id" type="string" />
<DynamicParamField body="feature_id" type="string" required>
ID of the feature to check access to.
</DynamicParamField>
<DynamicParamField body="entity_id" type="string">
If using entity balances (eg, seats), the entity ID to check access for.
@@ -33,10 +35,6 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx
If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation.
</DynamicParamField>
<DynamicParamField body="product_id" type="string" />
<DynamicParamField body="required_quantity" type="number" />
### Response

View File

@@ -4232,6 +4232,7 @@ paths:
description: ID which you provided when creating the customer
feature_id:
type: string
description: ID of the feature to check access to.
entity_id:
type: string
description: If using entity balances (eg, seats), the entity ID to check access
@@ -4256,12 +4257,9 @@ paths:
description: If true, the response will include a preview object, which can be
used to display information such as a paywall or upgrade
confirmation.
product_id:
type: string
required_quantity:
type: number
required:
- customer_id
- feature_id
responses:
"200":
description: OK
@@ -4831,6 +4829,7 @@ paths:
const result = await autumn.balances.check({
customerId: "<id>",
featureId: "<id>",
});
- lang: python
label: Python (SDK)
@@ -4839,7 +4838,10 @@ paths:
autumn = Autumn(secret_key="am_sk_test...")
res = autumn.balances.check(customer_id="<id>")
res = autumn.balances.check(
customer_id="<id>",
feature_id="<id>",
)
/v1/balances.track:
post:
operationId: balancesTrack

View File

@@ -64,11 +64,13 @@ export default function UseAutumnScenarioPage() {
// Input state
const [planId, setPlanId] = useState("");
const [featureId, setFeatureId] = useState("");
const [requiredBalance, setRequiredBalance] = useState("");
const [openInNewTab, setOpenInNewTab] = useState(false);
// Form element IDs
const planIdInputId = useId();
const featureIdInputId = useId();
const requiredBalanceInputId = useId();
const runAction = async ({
name,
@@ -123,10 +125,16 @@ export default function UseAutumnScenarioPage() {
const handleCheck = () => {
if (!featureId) return;
const params = {
featureId,
requiredBalance: requiredBalance
? parseFloat(requiredBalance)
: undefined,
};
runAction({
name: "check",
params: { featureId },
execute: () => check({ featureId }),
params,
execute: () => check(params),
});
};
@@ -225,6 +233,22 @@ export default function UseAutumnScenarioPage() {
className="h-8 text-sm"
/>
</div>
<div className="space-y-1.5">
<Label
htmlFor={requiredBalanceInputId}
className="text-xs text-zinc-500"
>
Required Balance (optional)
</Label>
<Input
id={requiredBalanceInputId}
type="number"
placeholder="Enter required balance"
value={requiredBalance}
onChange={(e) => setRequiredBalance(e.target.value)}
className="h-8 text-sm"
/>
</div>
<Button
size="sm"
disabled={isRunning || !featureId}

View File

@@ -6,6 +6,6 @@ const SIMULATE_UNAUTHENTICATED = false;
export const handler = autumnHandler({
secretKey: process.env.AUTUMN_SECRET_KEY,
baseURL: "http://localhost:8080",
autumnURL: "http://localhost:8080",
identify: async () => (SIMULATE_UNAUTHENTICATED ? null : SDK_TEST_IDENTITY),
});

View File

@@ -2,7 +2,8 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"paths": {
"@/*": ["./*"]
"@/*": ["./*"],
"@api/*": ["../../shared/api/*"]
}
},
"include": [

View File

@@ -35,6 +35,7 @@
],
"@sdk": ["../../packages/autumn-js/src/sdk/index.ts"],
"@sdk/*": ["../../packages/autumn-js/src/sdk/*"],
"@api/*": ["../../shared/api/*"],
"@utils/*": ["../../packages/autumn-js/src/utils/*"],
"autumn-js": ["../../packages/autumn-js/src/sdk/index.ts"],
"autumn-js/react": ["../../packages/autumn-js/src/react/index.ts"],

View File

@@ -168,7 +168,7 @@
},
"packages/sdk": {
"name": "@useautumn/sdk",
"version": "0.8.26",
"version": "0.8.27",
"dependencies": {
"zod": "^3.25.65 || ^4.0.0",
},

View File

@@ -17,7 +17,7 @@ actions:
secret_key="<YOUR_BEARER_TOKEN_HERE>",
) as autumn:
res = autumn.balances.check(customer_id="<id>")
res = autumn.balances.check(customer_id="<id>", feature_id="<id>")
# Handle response
print(res)

View File

@@ -1,16 +1,16 @@
lockVersion: 2.0.0
id: 05940b80-1ef8-40f4-9878-822fb2792070
management:
docChecksum: ae28cba9f4a31ec39633cc683b159eab
docChecksum: fec27a570216b53ac684c105645b6173
docVersion: 2.1.0
speakeasyVersion: 1.719.0
generationVersion: 2.824.1
releaseVersion: 0.2.22
configChecksum: 05fe4cc58f958fa21b320a1cbb0ec4d8
releaseVersion: 0.2.23
configChecksum: b49d9388b24bfc90f604822ef8177d32
persistentEdits:
generation_id: b8deb3e6-4376-4384-8fb8-d3742769316e
pristine_commit_hash: d81074b2463a2168ca16d2be775f9316dbcec5f0
pristine_tree_hash: b9f1e436d46ef67a51ccebe4176695d899ea2658
generation_id: 7ec28ef9-de11-4ffe-a994-4a2d11fa4783
pristine_commit_hash: 1cb483a63d6f596f6074489a6e9910875d391989
pristine_tree_hash: 44c77d4ff8b12e187ee9387f45bb54d132266c56
features:
python:
additionalDependencies: 1.0.0
@@ -125,8 +125,8 @@ trackedFiles:
pristine_git_object: 3e6f66868ff780f866d68fdd7875f51990446172
docs/models/balancescheckrequest.md:
id: 3f58aa2a0e56
last_write_checksum: sha1:da2a911efdfb3247c30564d9b44798ddc1064539
pristine_git_object: 88f4b9bd27b9fb0e9438c2b2ee95fb8d115bed5b
last_write_checksum: sha1:631eddef8c414fc980bee9b676dced772c276e24
pristine_git_object: 426327d989050aa71a9dc1d8e55f1e3b8bc9db4a
docs/models/balancescheckreset.md:
id: acd256151825
last_write_checksum: sha1:aaeb8d3a138366e3c03c7e5c45c7adcb22062190
@@ -1465,8 +1465,8 @@ trackedFiles:
pristine_git_object: 69dd549ec7f5f885101d08dd502e25748183aebf
docs/sdks/balancessdk/README.md:
id: 2484f934be2e
last_write_checksum: sha1:f7062478c95b5d1942725f202da8591b6997801d
pristine_git_object: 367d6f7e6677473533e5ec84423c6b92bb191627
last_write_checksum: sha1:a8dd55b3787ac362deacf763b0a211900a767e93
pristine_git_object: 7afb196a0fe5d64bbc8f5754febdc70e891f9783
docs/sdks/billing/README.md:
id: dc915331dd9d
last_write_checksum: sha1:e3669a666eb87cb510f80973d756454cb5124433
@@ -1489,8 +1489,8 @@ trackedFiles:
pristine_git_object: f13c2fb5532a06cfe87f594097bfbf72e8fc04a1
pyproject.toml:
id: 5d07e7d72637
last_write_checksum: sha1:1e1204524034eb1c9c0cbb4327c4ac5ad156a8e4
pristine_git_object: 38ecae9bb9c9bd37af5c19d7f6869e1a672f86bc
last_write_checksum: sha1:014fd5de1df29b5f35e76f1c635c021ad9a7a03b
pristine_git_object: 724b12dd08709c8cddac0a78d200b08ec654fc01
scripts/publish.sh:
id: fe273b08f514
last_write_checksum: sha1:adc9b741c12ad1591ab4870eabe20f0d0a86cd1a
@@ -1513,12 +1513,12 @@ trackedFiles:
pristine_git_object: 3e604651c1eae73d815b276806e73b2f1334bb79
src/autumn_sdk/_version.py:
id: a98babfdf4fc
last_write_checksum: sha1:956be51e7b757519eedbeb2ff25e3d59b24c8a18
pristine_git_object: 6e51775a922b9d5cdbd3cf7c928df326748a5255
last_write_checksum: sha1:587361ce6cbd0c8f082cc3835fe408873429de3b
pristine_git_object: 657096928db179a673873a7c2970bc56d0134bd9
src/autumn_sdk/balances_sdk.py:
id: a24aee6f517f
last_write_checksum: sha1:991efc1f4a5675c7c69983dc539b419fee18895b
pristine_git_object: 458d7a5d63adb489da6bb0292ae672085714e36b
last_write_checksum: sha1:92e60f2fca39931ac13bd2e9b76048c307a8393a
pristine_git_object: 10dad75191da7d12379beb6db1bce3a49a21361d
src/autumn_sdk/basesdk.py:
id: 8c9c35fe744d
last_write_checksum: sha1:5d6abacbd251bd806538b19ab9662994bbfc6e24
@@ -1561,8 +1561,8 @@ trackedFiles:
pristine_git_object: ef0446c2af1fb073acd7d9dbded6b04bfad1d0dd
src/autumn_sdk/models/balancescheckop.py:
id: 27ed9e0d5743
last_write_checksum: sha1:f76c84c4592ee5bee854d2d31ac55b589c54d86c
pristine_git_object: 3a48eb8b3bda977baaf9afa42abc969e736d649a
last_write_checksum: sha1:4c29af4c515b7d7248cb2bac03299483be523d7f
pristine_git_object: 23ee9fc6c157d5ed56a335112a7da96dfcee325c
src/autumn_sdk/models/balancescreateop.py:
id: 8509fb57571f
last_write_checksum: sha1:406d57f4df7fb4eba2a7c8f906cf64ff0b31eb2c
@@ -1880,7 +1880,7 @@ examples:
header:
x-api-version: "2.1"
requestBody:
application/json: {"customer_id": "<id>"}
application/json: {"customer_id": "<id>", "feature_id": "<id>"}
responses:
"200":
application/json: {"allowed": true, "customer_id": "<id>", "balance": {"feature_id": "<id>", "granted": 7002.31, "remaining": 9536.21, "usage": 3270.65, "unlimited": false, "overage_allowed": true, "max_purchase": 8252.55, "next_reset_at": 9394.31}}

View File

@@ -30,7 +30,7 @@ generation:
generateNewTests: true
skipResponseBodyAssertions: false
python:
version: 0.2.22
version: 0.2.23
additionalDependencies:
dev: {}
main: {}

View File

@@ -6,11 +6,9 @@
| Field | Type | Required | Description |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `customer_id` | *str* | :heavy_check_mark: | ID which you provided when creating the customer |
| `feature_id` | *Optional[str]* | :heavy_minus_sign: | N/A |
| `feature_id` | *str* | :heavy_check_mark: | ID of the feature to check access to. |
| `entity_id` | *Optional[str]* | :heavy_minus_sign: | If using entity balances (eg, seats), the entity ID to check access for. |
| `required_balance` | *Optional[float]* | :heavy_minus_sign: | If you know the amount of the feature the end user is consuming in advance. If their balance is below this quantity, allowed will be false. |
| `properties` | Dict[str, *Any*] | :heavy_minus_sign: | N/A |
| `send_event` | *Optional[bool]* | :heavy_minus_sign: | If true, a usage event will be recorded together with checking access. The required_balance field will be used as the usage value. |
| `with_preview` | *Optional[bool]* | :heavy_minus_sign: | If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation. |
| `product_id` | *Optional[str]* | :heavy_minus_sign: | N/A |
| `required_quantity` | *Optional[float]* | :heavy_minus_sign: | N/A |
| `with_preview` | *Optional[bool]* | :heavy_minus_sign: | If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation. |

View File

@@ -121,7 +121,7 @@ with Autumn(
secret_key="<YOUR_BEARER_TOKEN_HERE>",
) as autumn:
res = autumn.balances.check(customer_id="<id>")
res = autumn.balances.check(customer_id="<id>", feature_id="<id>")
# Handle response
print(res)
@@ -133,14 +133,12 @@ with Autumn(
| Parameter | Type | Required | Description |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `customer_id` | *str* | :heavy_check_mark: | ID which you provided when creating the customer |
| `feature_id` | *Optional[str]* | :heavy_minus_sign: | N/A |
| `feature_id` | *str* | :heavy_check_mark: | ID of the feature to check access to. |
| `entity_id` | *Optional[str]* | :heavy_minus_sign: | If using entity balances (eg, seats), the entity ID to check access for. |
| `required_balance` | *Optional[float]* | :heavy_minus_sign: | If you know the amount of the feature the end user is consuming in advance. If their balance is below this quantity, allowed will be false. |
| `properties` | Dict[str, *Any*] | :heavy_minus_sign: | N/A |
| `send_event` | *Optional[bool]* | :heavy_minus_sign: | If true, a usage event will be recorded together with checking access. The required_balance field will be used as the usage value. |
| `with_preview` | *Optional[bool]* | :heavy_minus_sign: | If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation. |
| `product_id` | *Optional[str]* | :heavy_minus_sign: | N/A |
| `required_quantity` | *Optional[float]* | :heavy_minus_sign: | N/A |
| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. |
### Response

View File

@@ -1,6 +1,6 @@
[project]
name = "autumn-sdk"
version = "0.2.22"
version = "0.2.23"
description = "Python SDK for the Autumn billing API"
authors = [{ name = "Autumn" },]
readme = "README.md"

View File

@@ -3,10 +3,10 @@
import importlib.metadata
__title__: str = "autumn-sdk"
__version__: str = "0.2.22"
__version__: str = "0.2.23"
__openapi_doc_version__: str = "2.1.0"
__gen_version__: str = "2.824.1"
__user_agent__: str = "speakeasy-sdk/python 0.2.22 2.824.1 2.1.0 autumn-sdk"
__user_agent__: str = "speakeasy-sdk/python 0.2.23 2.824.1 2.1.0 autumn-sdk"
try:
if __package__ is not None:

View File

@@ -473,14 +473,12 @@ class BalancesSDK(BaseSDK):
self,
*,
customer_id: str,
feature_id: Optional[str] = None,
feature_id: str,
entity_id: Optional[str] = None,
required_balance: Optional[float] = None,
properties: Optional[Dict[str, Any]] = None,
send_event: Optional[bool] = None,
with_preview: Optional[bool] = None,
product_id: Optional[str] = None,
required_quantity: Optional[float] = None,
retries: OptionalNullable[utils.RetryConfig] = UNSET,
server_url: Optional[str] = None,
timeout_ms: Optional[int] = None,
@@ -489,14 +487,12 @@ class BalancesSDK(BaseSDK):
r"""Check whether usage is allowed for a customer feature.
:param customer_id: ID which you provided when creating the customer
:param feature_id:
:param feature_id: ID of the feature to check access to.
:param entity_id: If using entity balances (eg, seats), the entity ID to check access for.
:param required_balance: If you know the amount of the feature the end user is consuming in advance. If their balance is below this quantity, allowed will be false.
:param properties:
:param send_event: If true, a usage event will be recorded together with checking access. The required_balance field will be used as the usage value.
:param with_preview: If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation.
:param product_id:
:param required_quantity:
:param retries: Override the default retry configuration for this method
:param server_url: Override the default server URL for this method
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
@@ -520,8 +516,6 @@ class BalancesSDK(BaseSDK):
properties=properties,
send_event=send_event,
with_preview=with_preview,
product_id=product_id,
required_quantity=required_quantity,
)
req = self._build_request(
@@ -587,14 +581,12 @@ class BalancesSDK(BaseSDK):
self,
*,
customer_id: str,
feature_id: Optional[str] = None,
feature_id: str,
entity_id: Optional[str] = None,
required_balance: Optional[float] = None,
properties: Optional[Dict[str, Any]] = None,
send_event: Optional[bool] = None,
with_preview: Optional[bool] = None,
product_id: Optional[str] = None,
required_quantity: Optional[float] = None,
retries: OptionalNullable[utils.RetryConfig] = UNSET,
server_url: Optional[str] = None,
timeout_ms: Optional[int] = None,
@@ -603,14 +595,12 @@ class BalancesSDK(BaseSDK):
r"""Check whether usage is allowed for a customer feature.
:param customer_id: ID which you provided when creating the customer
:param feature_id:
:param feature_id: ID of the feature to check access to.
:param entity_id: If using entity balances (eg, seats), the entity ID to check access for.
:param required_balance: If you know the amount of the feature the end user is consuming in advance. If their balance is below this quantity, allowed will be false.
:param properties:
:param send_event: If true, a usage event will be recorded together with checking access. The required_balance field will be used as the usage value.
:param with_preview: If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation.
:param product_id:
:param required_quantity:
:param retries: Override the default retry configuration for this method
:param server_url: Override the default server URL for this method
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
@@ -634,8 +624,6 @@ class BalancesSDK(BaseSDK):
properties=properties,
send_event=send_event,
with_preview=with_preview,
product_id=product_id,
required_quantity=required_quantity,
)
req = self._build_request_async(

View File

@@ -47,7 +47,8 @@ class BalancesCheckGlobals(BaseModel):
class BalancesCheckRequestTypedDict(TypedDict):
customer_id: str
r"""ID which you provided when creating the customer"""
feature_id: NotRequired[str]
feature_id: str
r"""ID of the feature to check access to."""
entity_id: NotRequired[str]
r"""If using entity balances (eg, seats), the entity ID to check access for."""
required_balance: NotRequired[float]
@@ -57,15 +58,14 @@ class BalancesCheckRequestTypedDict(TypedDict):
r"""If true, a usage event will be recorded together with checking access. The required_balance field will be used as the usage value."""
with_preview: NotRequired[bool]
r"""If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation."""
product_id: NotRequired[str]
required_quantity: NotRequired[float]
class BalancesCheckRequest(BaseModel):
customer_id: str
r"""ID which you provided when creating the customer"""
feature_id: Optional[str] = None
feature_id: str
r"""ID of the feature to check access to."""
entity_id: Optional[str] = None
r"""If using entity balances (eg, seats), the entity ID to check access for."""
@@ -81,22 +81,15 @@ class BalancesCheckRequest(BaseModel):
with_preview: Optional[bool] = None
r"""If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation."""
product_id: Optional[str] = None
required_quantity: Optional[float] = None
@model_serializer(mode="wrap")
def serialize_model(self, handler):
optional_fields = set(
[
"feature_id",
"entity_id",
"required_balance",
"properties",
"send_event",
"with_preview",
"product_id",
"required_quantity",
]
)
serialized = handler(self)

View File

@@ -44,7 +44,7 @@ wheels = [
[[package]]
name = "autumn-sdk"
version = "0.2.22"
version = "0.2.23"
source = { editable = "." }
dependencies = [
{ name = "httpcore" },

View File

@@ -0,0 +1,73 @@
import type { Customer } from "@useautumn/sdk";
type CustomerBalance = Customer["balances"][string];
type CustomerFeature = NonNullable<Customer["balances"][string]["feature"]>;
const getAvailableOverage = ({
balance,
}: {
balance: CustomerBalance;
}): number | undefined => {
const breakdown = balance.breakdown ?? [];
const usageBasedBreakdown = breakdown.filter(
(item) => item.price?.billingMethod === "usage_based",
);
if (usageBasedBreakdown.length === 0) {
return balance.maxPurchase ?? undefined;
}
let maxOverage = 0;
let overageUsage = 0;
for (const item of usageBasedBreakdown) {
if (item.price?.maxPurchase === null) {
return undefined;
}
if (item.price?.maxPurchase !== undefined) {
maxOverage += item.price.maxPurchase;
}
const overage = Math.max(
0,
item.usage - item.includedGrant - item.prepaidGrant,
);
overageUsage += overage;
}
return Math.max(0, maxOverage - overageUsage);
};
export const balanceToAllowed = ({
balance,
feature,
requiredBalance,
}: {
balance: CustomerBalance;
feature: CustomerFeature;
requiredBalance: number;
}) => {
if (feature.type === "boolean") {
return true;
}
if (balance.unlimited) {
return true;
}
if (requiredBalance < 0) {
return true;
}
if (balance.overageAllowed) {
const availableOverage = getAvailableOverage({ balance });
if (availableOverage === undefined) {
return true;
}
return balance.remaining + availableOverage >= requiredBalance;
}
return balance.remaining >= requiredBalance;
};

View File

@@ -0,0 +1,23 @@
import type { Customer } from "@useautumn/sdk";
type CustomerFeature = NonNullable<Customer["balances"][string]["feature"]>;
export const customerToFeatures = ({
customer,
}: {
customer: Customer;
}): CustomerFeature[] => {
const balances = Object.values(customer.balances);
if (balances.length === 0) return [];
const firstBalance = balances[0];
if (!firstBalance.feature) {
throw new Error(
"[customerToFeatures] please expand `balances.feature` to get features for the customer",
);
}
return balances
.map((balance) => balance.feature)
.filter((feature): feature is CustomerFeature => Boolean(feature));
};

View File

@@ -0,0 +1,20 @@
import type { Customer } from "@useautumn/sdk";
type CustomerFeature = NonNullable<Customer["balances"][string]["feature"]>;
export const findCreditSystemsByFeature = ({
featureId,
features,
}: {
featureId: string;
features: CustomerFeature[];
}) => {
return features.filter(
(feature) =>
feature.id !== featureId &&
feature.type === "credit_system" &&
feature.creditSchema?.some(
(schema) => schema.meteredFeatureId === featureId,
),
);
};

View File

@@ -0,0 +1,23 @@
import type { Customer } from "@useautumn/sdk";
type CustomerFeature = NonNullable<Customer["balances"][string]["feature"]>;
export const getCreditCost = ({
featureId,
creditSystem,
amount = 1,
}: {
featureId: string;
creditSystem: CustomerFeature;
amount?: number;
}) => {
if (creditSystem.type !== "credit_system") {
return amount;
}
const schemaItem = creditSystem.creditSchema?.find(
(schema) => schema.meteredFeatureId === featureId,
);
return amount * (schemaItem?.creditCost ?? 1);
};

View File

@@ -1,189 +0,0 @@
import type { BalancesCheckResponse, Customer } from "@useautumn/sdk";
import {
type ApiBalanceInput,
apiBalanceToAllowed,
} from "../../../../../../shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed";
import type { ClientCheckParams } from "../../../types/params";
import { toSnakeCase } from "../../../utils/toSnakeCase";
type CustomerBalance = Customer["balances"][string];
const resolveRequiredBalance = ({
requiredBalance,
requiredQuantity,
}: {
requiredBalance?: number;
requiredQuantity?: number;
}) => {
return requiredBalance ?? requiredQuantity ?? 1;
};
const isBalanceAllowed = ({
balance,
requiredBalance,
}: {
balance: CustomerBalance;
requiredBalance: number;
}) => {
const snakeCaseBalance = toSnakeCase({
obj: balance,
}) as unknown as ApiBalanceInput;
const featureForCheck = {
type: balance.feature?.type ?? "metered",
} as Parameters<typeof apiBalanceToAllowed>[0]["feature"];
return apiBalanceToAllowed({
apiBalance: snakeCaseBalance,
feature: featureForCheck,
requiredBalance,
});
};
const getCreditBalanceRequired = ({
creditBalance,
featureId,
requiredBalance,
}: {
creditBalance: CustomerBalance;
featureId: string;
requiredBalance: number;
}) => {
const creditCost =
creditBalance.feature?.creditSchema?.find(
(schema) => schema.meteredFeatureId === featureId,
)?.creditCost ?? 1;
return requiredBalance * creditCost;
};
const getFeatureCheckResponse = ({
customer,
params,
}: {
customer: Customer;
params: ClientCheckParams;
}): BalancesCheckResponse => {
const featureId = params.featureId;
if (!featureId) {
return {
allowed: false,
customerId: customer.id ?? "",
entityId: params.entityId ?? null,
requiredBalance: resolveRequiredBalance({
requiredBalance: params.requiredBalance,
requiredQuantity: params.requiredQuantity,
}),
balance: null,
};
}
const requiredBalance = resolveRequiredBalance({
requiredBalance: params.requiredBalance,
requiredQuantity: params.requiredQuantity,
});
const mainBalance = customer.balances[featureId];
const creditBalances = Object.values(customer.balances).filter((balance) =>
balance.feature?.creditSchema?.some(
(schema) => schema.meteredFeatureId === featureId,
),
);
if (
mainBalance &&
isBalanceAllowed({ balance: mainBalance, requiredBalance })
) {
return {
allowed: true,
customerId: customer.id ?? "",
entityId: params.entityId ?? null,
requiredBalance,
balance: mainBalance as BalancesCheckResponse["balance"],
};
}
for (const creditBalance of creditBalances) {
const creditRequiredBalance = getCreditBalanceRequired({
creditBalance,
featureId,
requiredBalance,
});
if (
isBalanceAllowed({
balance: creditBalance,
requiredBalance: creditRequiredBalance,
})
) {
return {
allowed: true,
customerId: customer.id ?? "",
entityId: params.entityId ?? null,
requiredBalance: creditRequiredBalance,
balance: creditBalance as BalancesCheckResponse["balance"],
};
}
}
if (mainBalance) {
return {
allowed: false,
customerId: customer.id ?? "",
entityId: params.entityId ?? null,
requiredBalance,
balance: mainBalance as BalancesCheckResponse["balance"],
};
}
if (creditBalances.length > 0) {
const firstCreditBalance = creditBalances[0];
return {
allowed: false,
customerId: customer.id ?? "",
entityId: params.entityId ?? null,
requiredBalance: getCreditBalanceRequired({
creditBalance: firstCreditBalance,
featureId,
requiredBalance,
}),
balance: firstCreditBalance as BalancesCheckResponse["balance"],
};
}
return {
allowed: false,
customerId: customer.id ?? "",
entityId: params.entityId ?? null,
requiredBalance,
balance: null,
};
};
export const getLocalCheckResponse = ({
customer,
params,
}: {
customer: Customer | null;
params: ClientCheckParams;
}): BalancesCheckResponse => {
if (!customer) {
return {
allowed: false,
customerId: "",
entityId: params.entityId ?? null,
requiredBalance: resolveRequiredBalance({
requiredBalance: params.requiredBalance,
requiredQuantity: params.requiredQuantity,
}),
balance: null,
};
}
if (!params.featureId) {
throw new Error("check() requires featureId");
}
return getFeatureCheckResponse({ customer, params });
};

View File

@@ -0,0 +1,147 @@
import type { BalancesCheckResponse, Customer } from "@useautumn/sdk";
import type { ClientCheckParams } from "../../../types/params";
import { balanceToAllowed } from "./check/balanceToAllowed";
import { customerToFeatures } from "./check/customerToFeatures";
import { findCreditSystemsByFeature } from "./check/findCreditSystemsByFeature";
import { getCreditCost } from "./check/getCreditCost";
type CustomerBalance = Customer["balances"][string];
type CustomerFeature = NonNullable<CustomerBalance["feature"]>;
const getFeatureToUse = ({
featureId,
features,
feature,
customer,
requiredBalance,
}: {
featureId: string;
features: CustomerFeature[];
feature: CustomerFeature;
customer: Customer;
requiredBalance: number;
}) => {
const creditSystems = findCreditSystemsByFeature({
featureId,
features,
});
if (creditSystems.length === 0) return feature;
const mainBalance = customer.balances[feature.id];
if (
mainBalance &&
balanceToAllowed({
balance: mainBalance,
feature,
requiredBalance,
})
) {
return feature;
}
for (const creditSystem of creditSystems) {
const creditBalance = customer.balances[creditSystem.id];
if (!creditBalance) continue;
const creditRequiredBalance = getCreditCost({
featureId: feature.id,
creditSystem,
amount: requiredBalance,
});
if (
balanceToAllowed({
balance: creditBalance,
feature: creditSystem,
requiredBalance: creditRequiredBalance,
})
) {
return creditSystem;
}
}
return creditSystems[0];
};
const getFeatureCheckResponse = ({
customer,
params,
}: {
customer: Customer;
params: ClientCheckParams;
}): BalancesCheckResponse => {
const { featureId, requiredBalance = 1 } = params;
const features = customerToFeatures({ customer });
const feature = features.find((item) => item.id === featureId);
if (!feature) {
throw new Error(`Feature ${featureId} not found`);
}
const featureToUse = getFeatureToUse({
featureId,
features,
feature,
customer,
requiredBalance,
});
const balanceToUse = customer.balances[featureToUse.id];
if (!balanceToUse) {
return {
allowed: false,
customerId: customer.id ?? "",
entityId: params.entityId ?? null,
requiredBalance,
balance: null,
};
}
const requiredBalanceToUse =
featureToUse.type === "credit_system" && featureToUse.id !== feature.id
? getCreditCost({
featureId: feature.id,
creditSystem: featureToUse,
amount: requiredBalance,
})
: requiredBalance;
const allowed = balanceToAllowed({
balance: balanceToUse,
feature: featureToUse,
requiredBalance: requiredBalanceToUse,
});
return {
allowed,
customerId: customer.id ?? "",
entityId: params.entityId ?? null,
requiredBalance: requiredBalanceToUse,
balance: balanceToUse as BalancesCheckResponse["balance"],
};
};
export const getLocalCheckResponse = ({
customer,
params,
}: {
customer: Customer | null;
params: ClientCheckParams;
}): BalancesCheckResponse => {
if (!customer) {
return {
allowed: false,
customerId: "",
entityId: params.entityId ?? null,
requiredBalance: params.requiredBalance ?? 1,
balance: null,
};
}
if (!params.featureId) {
throw new Error("check() requires featureId");
}
return getFeatureCheckResponse({ customer, params });
};

View File

@@ -8,7 +8,7 @@ import type {
import { useCallback } from "react";
import type { AttachParams, CheckParams } from "../../../types";
import type { IAutumnClient } from "../../client/IAutumnClient";
import { getLocalCheckResponse } from "./checkUtils";
import { getLocalCheckResponse } from "./getLocalCheckResponse";
const redirectToUrl = ({
url,

View File

@@ -3910,6 +3910,7 @@ paths:
description: ID which you provided when creating the customer
feature_id:
type: string
description: ID of the feature to check access to.
entity_id:
type: string
description: If using entity balances (eg, seats), the entity ID to check access
@@ -3934,12 +3935,9 @@ paths:
description: If true, the response will include a preview object, which can be
used to display information such as a paywall or upgrade
confirmation.
product_id:
type: string
required_quantity:
type: number
required:
- customer_id
- feature_id
responses:
"200":
description: OK

View File

@@ -3963,6 +3963,7 @@ paths:
description: ID which you provided when creating the customer
feature_id:
type: string
description: ID of the feature to check access to.
entity_id:
type: string
description: If using entity balances (eg, seats), the entity ID to check access
@@ -3987,12 +3988,9 @@ paths:
description: If true, the response will include a preview object, which can be
used to display information such as a paywall or upgrade
confirmation.
product_id:
type: string
required_quantity:
type: number
required:
- customer_id
- feature_id
responses:
"200":
description: OK

View File

@@ -1,8 +1,8 @@
import { SuccessResponseSchema } from "@api/common/commonResponses.js";
import {
CheckParamsSchema,
CheckResponseV3Schema,
CreateBalanceParamsV0Schema,
ExtCheckParamsSchema,
TrackParamsSchema,
TrackResponseV3Schema,
UpdateBalanceParamsSchema,
@@ -51,7 +51,7 @@ export const balancesCheckContract = oc
"x-speakeasy-name-override": "check",
}),
})
.input(CheckParamsSchema)
.input(ExtCheckParamsSchema)
.output(CheckResponseV3Schema);
export const balancesTrackContract = oc

View File

@@ -19,6 +19,7 @@ actions:
async function run() {
const result = await autumn.balances.check({
customerId: "<id>",
featureId: "<id>",
});
console.log(result);

View File

@@ -1,16 +1,16 @@
lockVersion: 2.0.0
id: 7b300647-cd76-49e9-bf77-7d1bf5446d66
management:
docChecksum: 2599354f8d9fec10f95f15d1c9096b1d
docChecksum: 41614313bae2edc9e0e3bbb7da66a4d6
docVersion: 2.1.0
speakeasyVersion: 1.719.0
generationVersion: 2.824.1
releaseVersion: 0.8.26
configChecksum: b6c3c32777c82006d83440f8a980d2af
releaseVersion: 0.8.27
configChecksum: 58626b5f963b3bd6dae32861f7223e4e
persistentEdits:
generation_id: ddc35772-b2eb-4820-b753-62525d82a8eb
pristine_commit_hash: 60a4122091e83ecafdd2621fef72c448d614bdf2
pristine_tree_hash: eea50d885bff1b9f808c6350c2e0ad0b877155a9
generation_id: e65473db-a4c6-4742-b95f-9b8012a90600
pristine_commit_hash: 9070b0faec440397f5c71122d64b390fcff54c5f
pristine_tree_hash: 2d507ad0bb4c7057b61076d8ab638fad99bd0e61
features:
typescript:
additionalDependencies: 0.1.0
@@ -144,8 +144,8 @@ trackedFiles:
pristine_git_object: 654cee9ccd3ac3ab7e478e50d5bb8e6689078728
docs/models/balances-check-request.md:
id: 9bc2f38fe309
last_write_checksum: sha1:84559e0a2c21e00b203b208b6e2c9f5b955652b0
pristine_git_object: 83840a6bac824db113dd2484c4819b65edaf68b1
last_write_checksum: sha1:0c05c1d4267bf87ccffbd12c2201ba77d3572b2e
pristine_git_object: 3c287ef0a9fb8ba29a9747f85cd7f75fe9deacdb
docs/models/balances-check-reset.md:
id: 0bc9bade2bd6
last_write_checksum: sha1:e62a7f8a2d10aba44af96cb2013ae8ef04bddbe7
@@ -1480,8 +1480,8 @@ trackedFiles:
pristine_git_object: 56e566eba7aeffa9422da7c6b4bcb80a123e5f14
docs/sdks/balances/README.md:
id: 6ca85866f00d
last_write_checksum: sha1:a9d2e85ca386a826d73b0d402cde2afcc2bab0b5
pristine_git_object: c7aa4176ebe0ecc174019273ac857b26e1986c2b
last_write_checksum: sha1:eece4ef5879f579768573ba82c46936733b88b1f
pristine_git_object: bf8790cb4364546e27a92b5705a0e7f25addeba1
docs/sdks/billing/README.md:
id: dc915331dd9d
last_write_checksum: sha1:9e5ac33eb2f8161d4a9813184ce0bda310b5184e
@@ -1516,12 +1516,12 @@ trackedFiles:
pristine_git_object: 900d545ed58929951e2208e1bec791cb264429a4
jsr.json:
id: 7f6ab7767282
last_write_checksum: sha1:81f59385b4f8527291ecad9b442981054bb1c9af
pristine_git_object: 498da37844b72100f84a7a0e8dab8e6011222af1
last_write_checksum: sha1:2f9caf84950826b0f80ea18b58f7a22fb1ff0b31
pristine_git_object: 1d4fc089e365e8a54c922d38d6937921e6d63ba0
package.json:
id: 7030d0b2f71b
last_write_checksum: sha1:1cf6dc171bd97df401d76a20b37cff97a8f79f2f
pristine_git_object: ac3ec23ec56922ecd27496c75330ab80bb4ce2c4
last_write_checksum: sha1:871236b8e1d9462e75ac2ba7a9e6654b2bc98a1d
pristine_git_object: a99b0d11bd80af6de52463ca1d05ef57bb5a831f
src/core.ts:
id: f431fdbcd144
last_write_checksum: sha1:f8f24a3ca09c1efb285d7a75ad3697d0128f47e2
@@ -1604,8 +1604,8 @@ trackedFiles:
pristine_git_object: 44be0eae8246521b230e8e711a88eff738fc015d
src/lib/config.ts:
id: 320761608fb3
last_write_checksum: sha1:cbc217ef66dbc1a7713cea3ed30b3afbe58f85f8
pristine_git_object: c80c88bd24b38e4183bb4602ef8df47e61d57381
last_write_checksum: sha1:600c912298c99f0e576d6ced27c9f13619a6e693
pristine_git_object: 7cb7a90dbda9a13ec9e20467e06cd2306df7146c
src/lib/dlv.ts:
id: b1988214835a
last_write_checksum: sha1:1dd3e3fbb4550c4bf31f5ef997faff355d6f3250
@@ -1672,8 +1672,8 @@ trackedFiles:
pristine_git_object: 7cfcf2c90f1cfaa152ee2f4033187aa20b1c696b
src/models/balances-check-op.ts:
id: b818c72b5764
last_write_checksum: sha1:43ba10dc6d3f77ac0c27566aa7da722be99b9b91
pristine_git_object: 202ad5a71f0bfd85e6d100d8aa477d2c106f4e35
last_write_checksum: sha1:ba49fee4c3f2246f0bb7ab82c4c737b64472e3d3
pristine_git_object: 00b739d168f088a2a6660855f985f6889ea7069f
src/models/balances-create-op.ts:
id: 0ac1d9c85f60
last_write_checksum: sha1:93f8b8a57403da4a95b83dcb2c7a54029694d2a4
@@ -2258,7 +2258,7 @@ examples:
header:
x-api-version: "2.1"
requestBody:
application/json: {"customer_id": "<id>"}
application/json: {"customer_id": "<id>", "feature_id": "<id>"}
responses:
"200":
application/json: {"allowed": true, "customer_id": "<id>", "balance": {"feature_id": "<id>", "granted": 7002.31, "remaining": 9536.21, "usage": 3270.65, "unlimited": false, "overage_allowed": true, "max_purchase": 8252.55, "next_reset_at": 9394.31}}

View File

@@ -33,7 +33,7 @@ generation:
generateNewTests: true
skipResponseBodyAssertions: false
typescript:
version: 0.8.26
version: 0.8.27
acceptHeaderEnum: false
additionalDependencies:
dependencies: {}

View File

@@ -3922,6 +3922,7 @@ paths:
description: ID which you provided when creating the customer
feature_id:
type: string
description: ID of the feature to check access to.
entity_id:
type: string
description: If using entity balances (eg, seats), the entity ID to check access for.
@@ -3939,12 +3940,9 @@ paths:
with_preview:
type: boolean
description: If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation.
product_id:
type: string
required_quantity:
type: number
required:
- customer_id
- feature_id
responses:
"200":
description: OK

View File

@@ -9,8 +9,8 @@ sources:
- 2.1.0
Autumn API Stripped:
sourceNamespace: autumn-api-stripped
sourceRevisionDigest: sha256:3c2b0984859b57cb45257efef3e1a1bfe48fd0ef1c2e3c3702d42b955cf096db
sourceBlobDigest: sha256:a10bc74dc833d54d6024f61f2d24fd5975f011ab69e88b489a46abb73c1eb4bd
sourceRevisionDigest: sha256:40c850f3f37a4ef63ace5e1bdfad2671d0f524de9cb7505fc9bd1cd61d90fbb6
sourceBlobDigest: sha256:d2a2be089c9d86271ff50f9910f11306fb30e365c419b0c255f9fba2a675cdc7
tags:
- latest
- 2.1.0
@@ -25,10 +25,10 @@ targets:
autumn-python:
source: Autumn API Stripped
sourceNamespace: autumn-api-stripped
sourceRevisionDigest: sha256:3c2b0984859b57cb45257efef3e1a1bfe48fd0ef1c2e3c3702d42b955cf096db
sourceBlobDigest: sha256:a10bc74dc833d54d6024f61f2d24fd5975f011ab69e88b489a46abb73c1eb4bd
sourceRevisionDigest: sha256:40c850f3f37a4ef63ace5e1bdfad2671d0f524de9cb7505fc9bd1cd61d90fbb6
sourceBlobDigest: sha256:d2a2be089c9d86271ff50f9910f11306fb30e365c419b0c255f9fba2a675cdc7
codeSamplesNamespace: autumn-api-python-code-samples
codeSamplesRevisionDigest: sha256:ca39ee64d522fc8fdba083d3fc8eef936be15f8f65340bb75bb6cce7b7190561
codeSamplesRevisionDigest: sha256:3cd6e4413b7a3d4adb59b7ae7c943dc291d6a2f8cf6416ddb8b286d1a56c9a47
workflow:
workflowVersion: 1.0.0
speakeasyVersion: pinned

View File

@@ -7,6 +7,7 @@ import { BalancesCheckRequest } from "@useautumn/sdk";
let value: BalancesCheckRequest = {
customerId: "<id>",
featureId: "<id>",
};
```
@@ -15,11 +16,9 @@ let value: BalancesCheckRequest = {
| Field | Type | Required | Description |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerId` | *string* | :heavy_check_mark: | ID which you provided when creating the customer |
| `featureId` | *string* | :heavy_minus_sign: | N/A |
| `featureId` | *string* | :heavy_check_mark: | ID of the feature to check access to. |
| `entityId` | *string* | :heavy_minus_sign: | If using entity balances (eg, seats), the entity ID to check access for. |
| `requiredBalance` | *number* | :heavy_minus_sign: | If you know the amount of the feature the end user is consuming in advance. If their balance is below this quantity, allowed will be false. |
| `properties` | Record<string, *any*> | :heavy_minus_sign: | N/A |
| `sendEvent` | *boolean* | :heavy_minus_sign: | If true, a usage event will be recorded together with checking access. The required_balance field will be used as the usage value. |
| `withPreview` | *boolean* | :heavy_minus_sign: | If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation. |
| `productId` | *string* | :heavy_minus_sign: | N/A |
| `requiredQuantity` | *number* | :heavy_minus_sign: | N/A |
| `withPreview` | *boolean* | :heavy_minus_sign: | If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation. |

View File

@@ -181,6 +181,7 @@ const autumn = new Autumn({
async function run() {
const result = await autumn.balances.check({
customerId: "<id>",
featureId: "<id>",
});
console.log(result);
@@ -207,6 +208,7 @@ const autumn = new AutumnCore({
async function run() {
const res = await balancesCheck(autumn, {
customerId: "<id>",
featureId: "<id>",
});
if (res.ok) {
const { value: result } = res;

View File

@@ -2,7 +2,7 @@
{
"name": "@useautumn/sdk",
"version": "0.8.26",
"version": "0.8.27",
"exports": {
".": "./src/index.ts",
"./models": "./src/models/index.ts",

View File

@@ -1,6 +1,6 @@
{
"name": "@useautumn/sdk",
"version": "0.8.26",
"version": "0.8.27",
"author": "Speakeasy",
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.js",

View File

@@ -66,7 +66,7 @@ export function serverURLFromOptions(options: SDKOptions): URL | null {
export const SDK_METADATA = {
language: "typescript",
openapiDocVersion: "2.1.0",
sdkVersion: "0.8.26",
sdkVersion: "0.8.27",
genVersion: "2.824.1",
userAgent: "speakeasy-sdk/typescript 0.8.26 2.824.1 2.1.0 @useautumn/sdk",
userAgent: "speakeasy-sdk/typescript 0.8.27 2.824.1 2.1.0 @useautumn/sdk",
} as const;

View File

@@ -21,7 +21,10 @@ export type BalancesCheckRequest = {
* ID which you provided when creating the customer
*/
customerId: string;
featureId?: string | undefined;
/**
* ID of the feature to check access to.
*/
featureId: string;
/**
* If using entity balances (eg, seats), the entity ID to check access for.
*/
@@ -39,8 +42,6 @@ export type BalancesCheckRequest = {
* If true, the response will include a preview object, which can be used to display information such as a paywall or upgrade confirmation.
*/
withPreview?: boolean | undefined;
productId?: string | undefined;
requiredQuantity?: number | undefined;
};
export const BalancesCheckBalanceType = {
@@ -490,14 +491,12 @@ export type BalancesCheckResponse = {
/** @internal */
export type BalancesCheckRequest$Outbound = {
customer_id: string;
feature_id?: string | undefined;
feature_id: string;
entity_id?: string | undefined;
required_balance?: number | undefined;
properties?: { [k: string]: any } | undefined;
send_event?: boolean | undefined;
with_preview?: boolean | undefined;
product_id?: string | undefined;
required_quantity?: number | undefined;
};
/** @internal */
@@ -507,14 +506,12 @@ export const BalancesCheckRequest$outboundSchema: z.ZodMiniType<
> = z.pipe(
z.object({
customerId: z.string(),
featureId: z.optional(z.string()),
featureId: z.string(),
entityId: z.optional(z.string()),
requiredBalance: z.optional(z.number()),
properties: z.optional(z.record(z.string(), z.any())),
sendEvent: z.optional(z.boolean()),
withPreview: z.optional(z.boolean()),
productId: z.optional(z.string()),
requiredQuantity: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
@@ -524,8 +521,6 @@ export const BalancesCheckRequest$outboundSchema: z.ZodMiniType<
requiredBalance: "required_balance",
sendEvent: "send_event",
withPreview: "with_preview",
productId: "product_id",
requiredQuantity: "required_quantity",
});
}),
);

View File

@@ -1,7 +1,7 @@
import { ApiFeatureType } from "@api/features/prevVersions/apiFeatureV0";
import type { EntInterval } from "@models/productModels/intervals/entitlementInterval";
import { resetIntvToEntIntv } from "@utils/productV2Utils/productItemUtils/convertProductItem/planItemIntervals";
import { Decimal } from "decimal";
import { Decimal } from "decimal.js";
import type { z } from "zod/v4";
import { FeatureType } from "../../../../models/featureModels/featureEnums";
import { sumValues } from "../../../../utils/utils";

View File

@@ -1,5 +1,5 @@
import { deduplicateArray } from "@utils/utils";
import { Decimal } from "decimal";
import { Decimal } from "decimal.js";
import type { ApiBalance, ApiBalanceBreakdown } from "../apiBalance";
import type { ApiBalanceBreakdownV1, ApiBalanceV1 } from "../apiBalanceV1";
import { apiBalanceV1ToPrepaidQuantity } from "../utils/convert/apiBalanceV1ToPrepaidQuantity";

View File

@@ -1,16 +1,9 @@
import {
type ApiBalanceInput,
type ApiCustomerV5,
type ApiEntityV2,
apiBalanceToAllowed,
type FeatureInput,
} from "../convert/apiBalanceToAllowed";
type CheckFeatureInput = FeatureInput & {
id: string;
};
type CheckEntityInput = {
balances?: Record<string, ApiBalanceInput | undefined>;
};
type Feature,
} from "../../../../../index";
export const getFeatureToUseForCheck = ({
creditSystems,
@@ -18,9 +11,9 @@ export const getFeatureToUseForCheck = ({
apiEntity,
requiredBalance,
}: {
creditSystems: CheckFeatureInput[];
feature: CheckFeatureInput;
apiEntity: CheckEntityInput;
creditSystems: Feature[];
feature: Feature;
apiEntity: ApiCustomerV5 | ApiEntityV2;
requiredBalance: number;
}) => {
// 1. If there's a credit system & cusEnts for that credit system -> return credit system

View File

@@ -1,36 +1,16 @@
import type { ApiBalanceV1 } from "@api/customers/cusFeatures/apiBalanceV1";
import { apiBalanceV1ToAvailableOverage } from "@api/customers/cusFeatures/utils/convert/apiBalanceV1ToAvailableOverage";
import type { Feature } from "@models/featureModels/featureModels";
import { isBooleanFeature, notNullish } from "@utils/index";
import { Decimal } from "decimal.js";
import { apiBalanceV1ToAvailableOverage } from "./apiBalanceV1ToAvailableOverage";
export type ApiBalanceBreakdownInput = {
usage: number;
included_grant: number;
prepaid_grant: number;
price?: {
billing_method?: string;
max_purchase: number | null;
} | null;
overage?: number;
};
export type ApiBalanceInput = {
unlimited: boolean;
overage_allowed: boolean;
remaining: number;
max_purchase: number | null;
breakdown?: ApiBalanceBreakdownInput[];
};
export type FeatureInput = {
type?: string;
};
export const apiBalanceToAllowed = ({
apiBalance,
feature,
requiredBalance,
}: {
apiBalance: ApiBalanceInput;
feature: FeatureInput;
apiBalance: ApiBalanceV1;
feature: Feature;
requiredBalance: number;
}) => {
if (!apiBalance) {
@@ -38,7 +18,7 @@ export const apiBalanceToAllowed = ({
}
// 1. Boolean
if (feature.type === "boolean") {
if (isBooleanFeature({ feature })) {
return true;
}
@@ -57,7 +37,7 @@ export const apiBalanceToAllowed = ({
// 1. Available overage
const availableOverage = apiBalanceV1ToAvailableOverage({ apiBalance });
if (availableOverage !== null && availableOverage !== undefined) {
if (notNullish(availableOverage)) {
return new Decimal(availableOverage)
.add(apiBalance.remaining)
.gte(requiredBalance);

View File

@@ -1,17 +1,12 @@
import { sumValues } from "@utils/utils";
import { Decimal } from "decimal.js";
import type {
ApiBalanceBreakdownInput,
ApiBalanceInput,
} from "./apiBalanceToAllowed";
import type { ApiBalanceBreakdownV1, ApiBalanceV1 } from "../../apiBalanceV1";
import { apiBalanceV1ToOverage } from "./apiBalanceV1ToOverage";
const sumValues = (values: number[]) =>
values.reduce((total, value) => total + value, 0);
export const apiBalanceBreakdownV1ToMaxOverage = ({
apiBalanceBreakdown,
}: {
apiBalanceBreakdown: ApiBalanceBreakdownInput;
apiBalanceBreakdown: ApiBalanceBreakdownV1;
}): number | undefined => {
if (apiBalanceBreakdown.price?.billing_method === "usage_based") {
return apiBalanceBreakdown.price?.max_purchase ?? undefined;
@@ -23,7 +18,7 @@ export const apiBalanceBreakdownV1ToMaxOverage = ({
export const apiBalanceV1ToMaxOverage = ({
apiBalance,
}: {
apiBalance: ApiBalanceInput;
apiBalance: ApiBalanceV1;
}): number | undefined => {
const breakdownItems = apiBalance.breakdown ?? [];
@@ -43,7 +38,7 @@ export const apiBalanceV1ToMaxOverage = ({
export const apiBalanceV1ToAvailableOverage = ({
apiBalance,
}: {
apiBalance: ApiBalanceInput;
apiBalance: ApiBalanceV1;
}): number | undefined => {
const maxOverage = apiBalanceV1ToMaxOverage({ apiBalance });
const overage = apiBalanceV1ToOverage({ apiBalance });

View File

@@ -1,15 +1,10 @@
import type {
ApiBalanceBreakdownInput,
ApiBalanceInput,
} from "./apiBalanceToAllowed";
const sumValues = (values: number[]) =>
values.reduce((total, value) => total + value, 0);
import { sumValues } from "@utils/utils";
import type { ApiBalanceBreakdownV1, ApiBalanceV1 } from "../../apiBalanceV1";
export const apiBalanceBreakdownV1ToOverage = ({
apiBalanceBreakdown,
}: {
apiBalanceBreakdown: ApiBalanceBreakdownInput;
apiBalanceBreakdown: ApiBalanceBreakdownV1;
}) => {
// const overage = Math.max(
// 0,
@@ -25,7 +20,7 @@ export const apiBalanceBreakdownV1ToOverage = ({
export const apiBalanceV1ToOverage = ({
apiBalance,
}: {
apiBalance: ApiBalanceInput;
apiBalance: ApiBalanceV1;
}) => {
const breakdownItems = apiBalance.breakdown ?? [];

View File

@@ -1,41 +1,22 @@
type ApiCustomerFeatureInput = {
id: string;
name?: string;
type?: string;
credit_schema?: Array<{
metered_feature_id: string;
credit_cost: number;
}>;
creditSchema?: Array<{
meteredFeatureId: string;
creditCost: number;
}>;
};
type ApiCustomerBalanceInput = {
feature?: ApiCustomerFeatureInput;
};
export type ApiCustomerWithBalancesInput = {
balances: Record<string, ApiCustomerBalanceInput>;
};
import type { ApiFeatureV1 } from "../../features/apiFeatureV1";
import type { ApiCustomerV5 } from "../apiCustomerV5";
export const apiCustomerToFeatures = ({
apiCustomer,
}: {
apiCustomer: ApiCustomerWithBalancesInput;
}) => {
apiCustomer: ApiCustomerV5;
}): ApiFeatureV1[] => {
const balances = Object.values(apiCustomer.balances);
if (balances.length === 0) return [];
const firstBalance = balances[0];
if (!firstBalance.feature) {
throw new Error(
"[customerToFeatures] please expand `balances.feature` to get features for the customer",
"[apiCustomerToFeatures] please expand `balances.feature` to get features for the customer",
);
}
return Object.values(apiCustomer.balances).map((balance) => {
return balance.feature;
}) as ApiCustomerFeatureInput[]; // safe to cast because we checked for feature in the first balance
return Object.values(apiCustomer.balances)
.map((balance) => balance.feature)
.filter((feature): feature is ApiFeatureV1 => feature !== undefined);
};

View File

@@ -1 +1 @@
export * from "./apiCustomerToFeatures";
export * from "./apiCustomerToFeatures.js";