@enyo-energy/energy-app-sdk 1.35.0 → 1.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +197 -0
  2. package/dist/cjs/energy-app-package-definition.d.cts +90 -0
  3. package/dist/cjs/energy-app-permission.type.cjs +2 -0
  4. package/dist/cjs/energy-app-permission.type.d.cts +3 -1
  5. package/dist/cjs/energy-app.cjs +29 -0
  6. package/dist/cjs/energy-app.d.cts +27 -0
  7. package/dist/cjs/enyo-energy-app-sdk.cjs +2 -0
  8. package/dist/cjs/enyo-energy-app-sdk.d.cts +9 -1
  9. package/dist/cjs/implementations/subscriptions/define-subscription-plan.cjs +124 -0
  10. package/dist/cjs/implementations/subscriptions/define-subscription-plan.d.cts +100 -0
  11. package/dist/cjs/implementations/subscriptions/in-memory-subscriptions.cjs +134 -0
  12. package/dist/cjs/implementations/subscriptions/in-memory-subscriptions.d.cts +97 -0
  13. package/dist/cjs/implementations/subscriptions/subscription-plan-validators.cjs +241 -0
  14. package/dist/cjs/implementations/subscriptions/subscription-plan-validators.d.cts +96 -0
  15. package/dist/cjs/index.cjs +7 -0
  16. package/dist/cjs/index.d.cts +7 -0
  17. package/dist/cjs/packages/energy-app-subscriptions.cjs +2 -0
  18. package/dist/cjs/packages/energy-app-subscriptions.d.cts +129 -0
  19. package/dist/cjs/packages/energy-app-webhook.cjs +2 -0
  20. package/dist/cjs/packages/energy-app-webhook.d.cts +91 -0
  21. package/dist/cjs/types/enyo-notification.d.cts +19 -1
  22. package/dist/cjs/types/enyo-subscription.cjs +81 -0
  23. package/dist/cjs/types/enyo-subscription.d.cts +253 -0
  24. package/dist/cjs/types/enyo-webhook.cjs +8 -0
  25. package/dist/cjs/types/enyo-webhook.d.cts +62 -0
  26. package/dist/cjs/version.cjs +1 -1
  27. package/dist/cjs/version.d.cts +1 -1
  28. package/dist/energy-app-package-definition.d.ts +90 -0
  29. package/dist/energy-app-permission.type.d.ts +3 -1
  30. package/dist/energy-app-permission.type.js +2 -0
  31. package/dist/energy-app.d.ts +27 -0
  32. package/dist/energy-app.js +29 -0
  33. package/dist/enyo-energy-app-sdk.d.ts +9 -1
  34. package/dist/enyo-energy-app-sdk.js +2 -0
  35. package/dist/implementations/subscriptions/define-subscription-plan.d.ts +100 -0
  36. package/dist/implementations/subscriptions/define-subscription-plan.js +116 -0
  37. package/dist/implementations/subscriptions/in-memory-subscriptions.d.ts +97 -0
  38. package/dist/implementations/subscriptions/in-memory-subscriptions.js +130 -0
  39. package/dist/implementations/subscriptions/subscription-plan-validators.d.ts +96 -0
  40. package/dist/implementations/subscriptions/subscription-plan-validators.js +234 -0
  41. package/dist/index.d.ts +7 -0
  42. package/dist/index.js +7 -0
  43. package/dist/packages/energy-app-subscriptions.d.ts +129 -0
  44. package/dist/packages/energy-app-subscriptions.js +1 -0
  45. package/dist/packages/energy-app-webhook.d.ts +91 -0
  46. package/dist/packages/energy-app-webhook.js +1 -0
  47. package/dist/types/enyo-notification.d.ts +19 -1
  48. package/dist/types/enyo-subscription.d.ts +253 -0
  49. package/dist/types/enyo-subscription.js +78 -0
  50. package/dist/types/enyo-webhook.d.ts +62 -0
  51. package/dist/types/enyo-webhook.js +5 -0
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/package.json +1 -1
package/README.md CHANGED
@@ -186,6 +186,8 @@ The SDK exposes several layered building blocks. Pick the one that matches the k
186
186
  | Query historical timeseries (PV, battery, meter, …) | [`useTimeseries()`](#usetimeseries-energyapptimeseries) |
187
187
  | Read site location (zip or coordinates) | [`useLocation()`](#uselocation-energyapplocation) |
188
188
  | Read grid connection point (fuse, phases, max power, charger limit) | [`useGridConnectionPoint()`](#usegridconnectionpoint-energyappgridconnectionpoint) |
189
+ | Receive webhooks from third-party services (one URL per app, parsed JSON body) | [`useWebhook()`](#usewebhook-energyappwebhook) |
190
+ | Sell one-time, monthly or yearly plans and check which ones the device has | [`useSubscriptions()`](#usesubscriptions-energyappsubscriptions) / [Subscription Plans](#subscription-plans) |
189
191
  | Retrieve secrets from the developer org secret store | [`useSecretManager()`](#usesecretmanager-energyappsecretmanager) |
190
192
  | Submit energy-manager diagnostics | [`useDiagnostics()`](#usediagnostics-energyappdiagnostics) |
191
193
  | Register a weather / PV / dynamic-price forecast provider | [`useWeatherForecasting()`](#useweatherforecasting-energyappweatherforecasting) / [`usePvForecasting()`](#usepvforecasting-energyapppvforecasting) / [`useDynamicPriceForecast()`](#usedynamicpriceforecast-energyappdynamicpriceforecast) |
@@ -242,6 +244,7 @@ energyApp.onShutdown(async () => {
242
244
  - `running`: App is functioning normally
243
245
  - `configuration-required`: App needs user configuration
244
246
  - `internet-connection-required`: App needs internet connectivity
247
+ - `subscription-required`: App needs a subscription plan; the host shows the plans declared in the package definition
245
248
 
246
249
  ### Package Definition
247
250
 
@@ -384,6 +387,7 @@ Energy Apps use a granular permissions system to control access to system resour
384
387
  - **`Wifi`**: List known WiFi SSIDs
385
388
  - **`Udp`**: Bind UDP sockets and exchange datagrams
386
389
  - **`ChildProcess`**: Spawn child processes from the runtime
390
+ - **`Webhook`**: Receive webhooks posted to the app's endpoint on the enyo API
387
391
 
388
392
  #### Data & Domain Permissions
389
393
 
@@ -401,6 +405,7 @@ Energy Apps use a granular permissions system to control access to system resour
401
405
  - **`EpexSpotPrices`**: Read EPEX SPOT day-ahead wholesale market prices
402
406
  - **`GridFeeRegister`** / **`GridFeeUse`**: Publish / resolve dynamic grid fees (time-variable network charges)
403
407
  - **`CommandLog`**: Record and read back the commands apps issued to appliances
408
+ - **`Subscriptions`**: Read the subscriptions bought for the app on this device
404
409
 
405
410
  #### Site & Identity Permissions
406
411
 
@@ -2249,6 +2254,81 @@ const failures = await commandLog.getCommands({
2249
2254
 
2250
2255
  Requires the `CommandLog` permission.
2251
2256
 
2257
+ #### `useWebhook(): EnergyAppWebhook`
2258
+
2259
+ Receive webhooks from a vendor cloud. Apps run behind NAT on the hub, so every app gets **one** public
2260
+ endpoint on the enyo API instead; whatever JSON body is POSTed to it is forwarded to the app as a parsed object.
2261
+
2262
+ ```typescript
2263
+ const webhook = energyApp.useWebhook();
2264
+
2265
+ const url = await webhook.getWebhookUrl(); // stable per app installation
2266
+ await vendorApi.subscribe({ callbackUrl: url });
2267
+
2268
+ const listenerId = webhook.listenForWebhook<VendorEvent>(async (event) => {
2269
+ // event: { webhookRequestId, source, receivedAtIso, body } — source defaults to 'custom-webhook'
2270
+ await handleVendorEvent(event.body); // already parsed JSON
2271
+ });
2272
+ ```
2273
+
2274
+ - **One endpoint per app.** Several event types share the same URL; tell them apart by the body.
2275
+ - **The body is already parsed.** `body` is the JSON object the caller sent. The type parameter is an
2276
+ assertion, not validation — check the fields you rely on.
2277
+ - **At-least-once delivery.** A call may be forwarded more than once and order is not guaranteed —
2278
+ deduplicate on `webhookRequestId`.
2279
+ - **Hidden sources need a token.** Some webhook sources are only delivered to listeners that present their static
2280
+ access token. Pass it per listener; the listener then receives these sources in addition to the public ones:
2281
+
2282
+ ```typescript
2283
+ webhook.listenForWebhook<VendorEvent>(handleEvent, {
2284
+ secretKeys: [{ source: 'vendor-cloud', token: vendorWebhookToken }]
2285
+ });
2286
+ ```
2287
+
2288
+ The token only applies to that listener, and a source with a wrong token is not delivered. Keep the token in the
2289
+ secret manager, not in the source code.
2290
+
2291
+ Requires the `Webhook` permission.
2292
+
2293
+ #### `useSubscriptions(): EnergyAppSubscriptions`
2294
+
2295
+ Read the subscriptions a customer bought for this app **on this device**. The plans and their prices are
2296
+ declared in the package definition (see [Subscription Plans](#subscription-plans)); enyo handles checkout,
2297
+ billing, renewal and cancellation. The app never starts a purchase — it reads the result and reacts to it.
2298
+
2299
+ ```typescript
2300
+ import { isPlanActive } from '@enyo-energy/energy-app-sdk';
2301
+
2302
+ const subscriptions = energyApp.useSubscriptions();
2303
+
2304
+ // On boot: read the current state.
2305
+ applyPlan(isPlanActive(await subscriptions.getSubscriptions(), 'premium'));
2306
+
2307
+ // Afterwards: purchases, renewals, cancellations, failed payments and expiry.
2308
+ subscriptions.listenForSubscriptionChanges(async ({ subscription, previousStatus }) => {
2309
+ // previousStatus is undefined for a new subscription
2310
+ applyPlan(isPlanActive(await subscriptions.getSubscriptions(), 'premium'));
2311
+ });
2312
+ ```
2313
+
2314
+ - **Use `isPlanActive()` / `isSubscriptionActive()`** instead of comparing statuses yourself. `trialing`, `active`,
2315
+ `cancelled-until-period-end` and `grace` count as active; `expired` does not.
2316
+ - **`getSubscriptions()` returns expired subscriptions too**, so the app can tell "never bought" from "expired".
2317
+ - **The listener does not replay the current state.** Call `getSubscriptions()` on boot.
2318
+ - **Works offline.** The host keeps the last known state on the device and reports time-based changes (end of
2319
+ trial, grace or paid period) without a connection.
2320
+ - **Fall back safely.** When a plan stops being active, release any control over appliances instead of leaving
2321
+ them in the last commanded state.
2322
+ - **Per-unit plans:** report the units with `setUnits(planKey, units)` and check each one with
2323
+ `isUnitActive(subscriptions, planKey, unit)` — see [Per-unit plans](#per-unit-plans).
2324
+ - **Point the user to an offer** with a notification target `{ type: 'subscription-offer', planKey: 'premium' }`,
2325
+ or set `EnergyAppStateEnum.SubscriptionRequired` when the whole app needs a plan.
2326
+
2327
+ For tests, `InMemorySubscriptions` implements the same interface; `upsertSubscription()` plays the part of enyo
2328
+ and notifies the listeners.
2329
+
2330
+ Requires the `Subscriptions` permission.
2331
+
2252
2332
  ### Operational Utilities
2253
2333
 
2254
2334
  #### `useOnboardingV2(): EnergyAppOnboardingV2`
@@ -2857,6 +2937,123 @@ It checks that names are unique slugs, that paths stay inside the package and ca
2857
2937
  extension, and that any declared MIME type is well formed; two names pointing at one
2858
2938
  path is a warning.
2859
2939
 
2940
+ ## Subscription Plans
2941
+
2942
+ Sell a one-time purchase or a monthly or yearly subscription for the whole app or for parts of it. Declare the plans, **including their prices**,
2943
+ in the package definition. The enyo CLI reads them during `enyo release`; enyo offers them to the customer and
2944
+ handles checkout, VAT, invoices, renewal and cancellation. Subscriptions belong to the **device** the app runs on.
2945
+
2946
+ ```typescript
2947
+ import {
2948
+ defineEnergyAppPackage,
2949
+ defineSubscriptionPlan,
2950
+ EnergyAppPermissionTypeEnum,
2951
+ EnyoCurrencyEnum,
2952
+ EnyoSubscriptionPaymentIntervalEnum,
2953
+ validatePackageSubscriptionPlans
2954
+ } from '@enyo-energy/energy-app-sdk';
2955
+
2956
+ const packageDef = defineEnergyAppPackage({
2957
+ // ...
2958
+ permissions: [EnergyAppPermissionTypeEnum.Subscriptions /* , ... */],
2959
+ subscriptionPlans: [
2960
+ defineSubscriptionPlan({
2961
+ planKey: 'premium',
2962
+ paymentInterval: EnyoSubscriptionPaymentIntervalEnum.Yearly,
2963
+ prices: [{ amountWithoutTax: 41.18, currency: EnyoCurrencyEnum.EUR }],
2964
+ trialDays: 30,
2965
+ storeEntry: [
2966
+ {
2967
+ language: 'en',
2968
+ title: 'Premium',
2969
+ shortDescription: 'Optimises your heat pump with dynamic prices.',
2970
+ benefits: ['Dynamic price optimisation', 'Heat pump boost']
2971
+ },
2972
+ {
2973
+ language: 'de',
2974
+ title: 'Premium',
2975
+ shortDescription: 'Optimiert deine Wärmepumpe mit dynamischen Preisen.',
2976
+ benefits: ['Optimierung nach dynamischen Preisen', 'Wärmepumpen-Boost']
2977
+ }
2978
+ ]
2979
+ })
2980
+ ]
2981
+ });
2982
+ ```
2983
+
2984
+ | Field | Meaning |
2985
+ |---|---|
2986
+ | `planKey` | Kebab-case slug, unique in the package. Subscriptions refer to the plan by it — treat it as an API. |
2987
+ | `paymentInterval` | `one-time`, `monthly` or `yearly`. One-time purchases never renew and stay `active` until refunded. |
2988
+ | `pricingModel` | `per-package` (default): one price for the package on the device. `per-unit`: the price is charged per unit the app reports — see [Per-unit plans](#per-unit-plans). |
2989
+ | `prices` | One net price per currency. `amountWithoutTax` is always the **total for one interval** — per month, per year, or the single one-time amount; per unit for `per-unit` plans — in the currency (not cent), at most two decimals; enyo adds VAT. |
2990
+ | `trialDays` | Optional free trial in days. Not allowed for `one-time` plans. |
2991
+ | `storeEntry` | Title, short description and optional benefits; `de` and `en` are required. |
2992
+ | `internalComment` | Optional note, never shown to users. |
2993
+
2994
+ What a plan unlocks is decided by the app: check `isPlanActive(subscriptions, planKey)` at runtime (see
2995
+ [`useSubscriptions()`](#usesubscriptions-energyappsubscriptions)).
2996
+
2997
+ ### Per-unit plans
2998
+
2999
+ For a `per-unit` plan the price is charged once per unit, and the **quantity comes from the energy app**. The app
3000
+ reports every unit it currently manages with `setUnits()` — on boot and whenever a unit is added or removed. Each
3001
+ unit has a `type` and a `referenceId` it is paid under:
3002
+
3003
+ | `type` | `referenceId` | Name shown to the customer |
3004
+ |---|---|---|
3005
+ | `appliance` | the appliance id | taken from the appliance |
3006
+ | `vehicle` | the vehicle id | taken from the vehicle |
3007
+ | `custom` | an id chosen by the app | `name`, translated; `de` and `en` are required |
3008
+
3009
+ ```typescript
3010
+ import { EnyoSubscriptionUnitTypeEnum, isUnitActive } from '@enyo-energy/energy-app-sdk';
3011
+
3012
+ const subscriptions = energyApp.useSubscriptions();
3013
+ const wallboxes = await energyApp.useAppliances().list();
3014
+
3015
+ // 3 wallboxes on a yearly plan for 10 € → 30 € per year (net).
3016
+ await subscriptions.setUnits('smart-charging', wallboxes.map(wallbox => ({
3017
+ type: EnyoSubscriptionUnitTypeEnum.Appliance,
3018
+ referenceId: wallbox.id
3019
+ })));
3020
+
3021
+ // A custom unit carries its own translated name.
3022
+ await subscriptions.setUnits('room-climate', [{
3023
+ type: EnyoSubscriptionUnitTypeEnum.Custom,
3024
+ referenceId: 'living-room',
3025
+ name: [{ language: 'en', value: 'Living room' }, { language: 'de', value: 'Wohnzimmer' }]
3026
+ }]);
3027
+
3028
+ // Only the units the customer paid for are active.
3029
+ const all = await subscriptions.getSubscriptions();
3030
+ for (const wallbox of wallboxes) {
3031
+ setSmartCharging(wallbox, isUnitActive(all, 'smart-charging', { type: 'appliance', referenceId: wallbox.id }));
3032
+ }
3033
+ ```
3034
+
3035
+ - **Reporting a unit does not pay for it.** Paid units are listed in `subscription.units`; when the customer pays
3036
+ for more units, the change listeners are called.
3037
+ - **Keep `referenceId` stable.** `type` plus `referenceId` identify the unit; a new reference is a new, unpaid unit.
3038
+ - **enyo decides how a changed quantity is charged**, e.g. pro rata for the rest of the period.
3039
+ - `validateSubscriptionUnits(units)` applies the host's rules up front: known type, non-empty reference, no unit
3040
+ twice, `de` and `en` names on custom units.
3041
+
3042
+ **A price change needs a new release** and only applies to new purchases — running subscriptions keep the price
3043
+ they were bought at.
3044
+
3045
+ Validate the plans before releasing:
3046
+
3047
+ ```typescript
3048
+ const { ok, errors } = validatePackageSubscriptionPlans(packageDef);
3049
+ // or: assertValidPackageSubscriptionPlans(packageDef) — throws PackageSubscriptionPlansValidationError
3050
+ ```
3051
+
3052
+ It checks that plan keys are unique slugs, that the payment interval and pricing model are known, that every plan has at least one
3053
+ positive price per currency with at most two decimals, that `trialDays` is a positive whole number and not set on a
3054
+ `one-time` plan, that `de` and
3055
+ `en` store entries exist, and that the package requests the `Subscriptions` permission.
3056
+
2860
3057
  ## Printable Manual Setup Guide
2861
3058
 
2862
3059
  A package can declare **one optional** onboarding guide (v2) as static data in its package
@@ -2,6 +2,7 @@ import { EnergyAppPermissionType } from "./energy-app-permission.type.cjs";
2
2
  import { EnergyAppModelFeatureEnum } from "./energy-app-model-feature.enum.cjs";
3
3
  import type { EnyoFileTranslation } from "./types/enyo-file.cjs";
4
4
  import type { EnyoOnboardingV2Guide } from "./types/enyo-onboarding-v2.cjs";
5
+ import type { EnyoSubscriptionPaymentInterval, EnyoSubscriptionPrice, EnyoSubscriptionPricingModel } from "./types/enyo-subscription.cjs";
5
6
  export type EnergyAppPackageLanguage = 'de' | 'en';
6
7
  export declare enum EnergyAppPackageCategory {
7
8
  Inverter = "inverter",
@@ -281,6 +282,82 @@ export interface EnergyAppPackageStoreEntry {
281
282
  /** Detailed description of the package functionality */
282
283
  description: string;
283
284
  }
285
+ /**
286
+ * Localized store information for one subscription plan. Shown by enyo on the
287
+ * offer and checkout screens and in the customer's subscription overview.
288
+ */
289
+ export interface EnergyAppPackageSubscriptionPlanStoreEntry {
290
+ /** Language code for this store entry */
291
+ language: EnergyAppPackageLanguage;
292
+ /** Display name of the plan, e.g. `'Premium'` */
293
+ title: string;
294
+ /** One sentence describing what the plan unlocks */
295
+ shortDescription: string;
296
+ /** Optional list of benefits, shown as bullet points on the offer screen */
297
+ benefits?: string[];
298
+ }
299
+ /**
300
+ * A subscription plan an Energy App package sells, declared in
301
+ * {@link EnergyAppPackageDefinition.subscriptionPlans}.
302
+ *
303
+ * A plan can unlock the whole app or only parts of it — the app decides that
304
+ * in its own code by checking whether the plan is active (see
305
+ * {@link EnergyAppSubscriptions}). enyo handles checkout, billing, renewal and
306
+ * cancellation; the plan only describes what is sold and at which price.
307
+ *
308
+ * The enyo CLI reads the plans at release time. A price change therefore
309
+ * requires a new release, and only applies to new purchases: running
310
+ * subscriptions keep the price they were bought at.
311
+ *
312
+ * @example
313
+ * ```typescript
314
+ * defineSubscriptionPlan({
315
+ * planKey: 'premium',
316
+ * paymentInterval: EnyoSubscriptionPaymentIntervalEnum.Yearly,
317
+ * prices: [{amountWithoutTax: 41.18, currency: EnyoCurrencyEnum.EUR}],
318
+ * trialDays: 30,
319
+ * storeEntry: [
320
+ * {language: 'en', title: 'Premium', shortDescription: 'Optimises your heat pump with dynamic prices.'},
321
+ * {language: 'de', title: 'Premium', shortDescription: 'Optimiert deine Wärmepumpe mit dynamischen Preisen.'}
322
+ * ]
323
+ * })
324
+ * ```
325
+ */
326
+ export interface EnergyAppPackageSubscriptionPlan {
327
+ /**
328
+ * Stable, app-chosen key for this plan: a kebab-case slug, unique within
329
+ * the package. Subscriptions refer to the plan by this key, so treat it as
330
+ * an API — renaming it disconnects every existing subscription from the
331
+ * app's checks.
332
+ */
333
+ planKey: string;
334
+ /** How often the plan is paid for: `one-time`, `monthly` or `yearly`. */
335
+ paymentInterval: EnyoSubscriptionPaymentInterval;
336
+ /**
337
+ * Whether the price is charged once for the package on the device
338
+ * (`per-package`, the default) or once per unit (`per-unit`). For
339
+ * `per-unit` the app reports the units — appliances, vehicles or custom
340
+ * units — at runtime with {@link EnergyAppSubscriptions.setUnits}.
341
+ */
342
+ pricingModel?: EnyoSubscriptionPricingModel;
343
+ /**
344
+ * Net price, one entry per currency. At least one entry is required. The
345
+ * amount is always the total for one interval: per month for `monthly`,
346
+ * per year for `yearly`, and the single amount for `one-time`. For a
347
+ * `per-unit` plan it is the amount per unit.
348
+ */
349
+ prices: EnyoSubscriptionPrice[];
350
+ /**
351
+ * Optional length of a free trial in days. The customer is charged for the
352
+ * first time when the trial ends, unless they cancelled before. Not
353
+ * allowed for `one-time` plans.
354
+ */
355
+ trialDays?: number;
356
+ /** Localized store information. Entries for `de` and `en` are required. */
357
+ storeEntry: EnergyAppPackageSubscriptionPlanStoreEntry[];
358
+ /** Optional internal note explaining this plan; never shown to users. */
359
+ internalComment?: string;
360
+ }
284
361
  /**
285
362
  * Defines a permission entry for an Energy App package.
286
363
  * Contains the permission type and an internal comment explaining its usage.
@@ -800,6 +877,19 @@ export interface EnergyAppPackageDefinition {
800
877
  * Omit the field for packages that have no manual setup to document.
801
878
  */
802
879
  onboardingGuide?: EnyoOnboardingV2Guide;
880
+ /**
881
+ * Subscription plans this package sells, including their prices. The enyo
882
+ * CLI reads them at release time; enyo offers them to the customer and
883
+ * handles checkout, billing, renewal and cancellation.
884
+ *
885
+ * Subscriptions belong to the device the app runs on. The app reads them
886
+ * at runtime through {@link EnergyAppSubscriptions}, which requires the
887
+ * `Subscriptions` permission. Validate the declarations with
888
+ * `validatePackageSubscriptionPlans()` before publishing.
889
+ *
890
+ * Omit for packages that are free.
891
+ */
892
+ subscriptionPlans?: EnergyAppPackageSubscriptionPlan[];
803
893
  }
804
894
  /**
805
895
  * Defines an Energy App package with automatic SDK version injection.
@@ -67,4 +67,6 @@ var EnergyAppPermissionTypeEnum;
67
67
  EnergyAppPermissionTypeEnum["CommandLog"] = "CommandLog";
68
68
  EnergyAppPermissionTypeEnum["Calibration"] = "Calibration";
69
69
  EnergyAppPermissionTypeEnum["VehicleIntegration"] = "VehicleIntegration";
70
+ EnergyAppPermissionTypeEnum["Webhook"] = "Webhook";
71
+ EnergyAppPermissionTypeEnum["Subscriptions"] = "Subscriptions";
70
72
  })(EnergyAppPermissionTypeEnum || (exports.EnergyAppPermissionTypeEnum = EnergyAppPermissionTypeEnum = {}));
@@ -62,7 +62,9 @@ export declare enum EnergyAppPermissionTypeEnum {
62
62
  GridFeeUse = "GridFeeUse",
63
63
  CommandLog = "CommandLog",
64
64
  Calibration = "Calibration",
65
- VehicleIntegration = "VehicleIntegration"
65
+ VehicleIntegration = "VehicleIntegration",
66
+ Webhook = "Webhook",
67
+ Subscriptions = "Subscriptions"
66
68
  }
67
69
  /**
68
70
  * String union of every permission name, derived from
@@ -523,6 +523,35 @@ class EnergyApp {
523
523
  useCascade() {
524
524
  return this.energyAppSdk.useCascade();
525
525
  }
526
+ /**
527
+ * Gets the Webhook API for receiving webhooks from third-party services.
528
+ *
529
+ * Every app has exactly one webhook endpoint on the enyo API. Read its URL
530
+ * with `getWebhookUrl()`, hand it to the vendor, and every JSON body posted
531
+ * to it is forwarded as a parsed object to the listeners registered with
532
+ * `listenForWebhook()`.
533
+ * @returns The Webhook API instance
534
+ * @throws {EnergyAppPermissionNotGrantedError} If the `Webhook`
535
+ * permission is not granted.
536
+ */
537
+ useWebhook() {
538
+ return this.energyAppSdk.useWebhook();
539
+ }
540
+ /**
541
+ * Gets the Subscriptions API for reading the subscriptions a customer
542
+ * bought for this app on this device.
543
+ *
544
+ * The plans and their prices are declared in the package definition;
545
+ * enyo handles checkout, billing, renewal and cancellation. The app reads
546
+ * the result with `getSubscriptions()` and reacts to purchases, renewals,
547
+ * cancellations and expiry with `listenForSubscriptionChanges()`.
548
+ * @returns The Subscriptions API instance
549
+ * @throws {EnergyAppPermissionNotGrantedError} If the `Subscriptions`
550
+ * permission is not granted.
551
+ */
552
+ useSubscriptions() {
553
+ return this.energyAppSdk.useSubscriptions();
554
+ }
526
555
  /**
527
556
  * Gets the current SDK version.
528
557
  * @returns The semantic version string of the SDK
@@ -51,6 +51,8 @@ import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.cj
51
51
  import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.cjs";
52
52
  import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
53
53
  import { EnergyAppCascade } from "./packages/energy-app-cascade.cjs";
54
+ import { EnergyAppWebhook } from "./packages/energy-app-webhook.cjs";
55
+ import { EnergyAppSubscriptions } from "./packages/energy-app-subscriptions.cjs";
54
56
  import { UseFetchOptions } from "./types/enyo-fetch.cjs";
55
57
  /**
56
58
  * Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
@@ -442,6 +444,31 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
442
444
  * called method is not granted.
443
445
  */
444
446
  useCascade(): EnergyAppCascade;
447
+ /**
448
+ * Gets the Webhook API for receiving webhooks from third-party services.
449
+ *
450
+ * Every app has exactly one webhook endpoint on the enyo API. Read its URL
451
+ * with `getWebhookUrl()`, hand it to the vendor, and every JSON body posted
452
+ * to it is forwarded as a parsed object to the listeners registered with
453
+ * `listenForWebhook()`.
454
+ * @returns The Webhook API instance
455
+ * @throws {EnergyAppPermissionNotGrantedError} If the `Webhook`
456
+ * permission is not granted.
457
+ */
458
+ useWebhook(): EnergyAppWebhook;
459
+ /**
460
+ * Gets the Subscriptions API for reading the subscriptions a customer
461
+ * bought for this app on this device.
462
+ *
463
+ * The plans and their prices are declared in the package definition;
464
+ * enyo handles checkout, billing, renewal and cancellation. The app reads
465
+ * the result with `getSubscriptions()` and reacts to purchases, renewals,
466
+ * cancellations and expiry with `listenForSubscriptionChanges()`.
467
+ * @returns The Subscriptions API instance
468
+ * @throws {EnergyAppPermissionNotGrantedError} If the `Subscriptions`
469
+ * permission is not granted.
470
+ */
471
+ useSubscriptions(): EnergyAppSubscriptions;
445
472
  /**
446
473
  * Gets the current SDK version.
447
474
  * @returns The semantic version string of the SDK
@@ -9,4 +9,6 @@ var EnergyAppStateEnum;
9
9
  EnergyAppStateEnum["ConfigurationRequired"] = "configuration-required";
10
10
  /** This state tells the user that an internet connection of the system is required which blocks the energy app from running*/
11
11
  EnergyAppStateEnum["InternetConnectionRequired"] = "internet-connection-required";
12
+ /** This state tells the user that a subscription plan is required which blocks the energy app from running. The host shows the plans declared in the package definition. */
13
+ EnergyAppStateEnum["SubscriptionRequired"] = "subscription-required";
12
14
  })(EnergyAppStateEnum || (exports.EnergyAppStateEnum = EnergyAppStateEnum = {}));
@@ -50,6 +50,8 @@ import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.cj
50
50
  import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.cjs";
51
51
  import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
52
52
  import { EnergyAppCascade } from "./packages/energy-app-cascade.cjs";
53
+ import { EnergyAppWebhook } from "./packages/energy-app-webhook.cjs";
54
+ import { EnergyAppSubscriptions } from "./packages/energy-app-subscriptions.cjs";
53
55
  import { UseFetchOptions } from "./types/enyo-fetch.cjs";
54
56
  export declare enum EnergyAppStateEnum {
55
57
  Launching = "launching",
@@ -57,7 +59,9 @@ export declare enum EnergyAppStateEnum {
57
59
  /** This state tells the user that a configuration is required which blocks the energy app from running*/
58
60
  ConfigurationRequired = "configuration-required",
59
61
  /** This state tells the user that an internet connection of the system is required which blocks the energy app from running*/
60
- InternetConnectionRequired = "internet-connection-required"
62
+ InternetConnectionRequired = "internet-connection-required",
63
+ /** This state tells the user that a subscription plan is required which blocks the energy app from running. The host shows the plans declared in the package definition. */
64
+ SubscriptionRequired = "subscription-required"
61
65
  }
62
66
  /**
63
67
  * Main API interface for enyo Energy App packages.
@@ -184,4 +188,8 @@ export interface EnyoEnergyAppSdk {
184
188
  useCommandLog: () => EnergyAppCommandLog;
185
189
  /** Get the Cascade API for the meter behind the primary meter: whether it is active, which appliances are behind it, and its own electricity tariff, prices and grid fee */
186
190
  useCascade: () => EnergyAppCascade;
191
+ /** Get the Webhook API for reading this app's single webhook URL on the enyo API and receiving the raw JSON bodies posted to it */
192
+ useWebhook: () => EnergyAppWebhook;
193
+ /** Get the Subscriptions API for reading the subscriptions bought for this app on this device and listening for new or changed subscriptions */
194
+ useSubscriptions: () => EnergyAppSubscriptions;
187
195
  }
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ /**
3
+ * Authoring and runtime helpers for subscription plans
4
+ * ({@link EnergyAppPackageDefinition.subscriptionPlans}).
5
+ *
6
+ * `defineSubscriptionPlan()` is an identity helper that type-checks a plan
7
+ * where it is written — mirroring `definePublicFile()` and
8
+ * `defineFirmwareFile()`. `isSubscriptionActive()` and `isPlanActive()` hold
9
+ * the single rule for which subscription statuses grant access, so every app
10
+ * applies it the same way; `isUnitActive()` applies it to one unit of a
11
+ * `per-unit` plan.
12
+ *
13
+ * Pair with `validatePackageSubscriptionPlans()`
14
+ * (`./subscription-plan-validators.ts`) to fail fast before publishing.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.ACTIVE_SUBSCRIPTION_STATUSES = void 0;
18
+ exports.defineSubscriptionPlan = defineSubscriptionPlan;
19
+ exports.isSubscriptionActive = isSubscriptionActive;
20
+ exports.isPlanActive = isPlanActive;
21
+ exports.isSameSubscriptionUnit = isSameSubscriptionUnit;
22
+ exports.isUnitActive = isUnitActive;
23
+ const enyo_subscription_js_1 = require("../../types/enyo-subscription.cjs");
24
+ /**
25
+ * Subscription statuses in which the customer may use what a plan unlocks.
26
+ *
27
+ * A cancelled subscription stays usable until its paid period ends, and a
28
+ * subscription whose renewal payment failed stays usable during the grace
29
+ * period.
30
+ */
31
+ exports.ACTIVE_SUBSCRIPTION_STATUSES = [
32
+ enyo_subscription_js_1.EnyoSubscriptionStatusEnum.Trialing,
33
+ enyo_subscription_js_1.EnyoSubscriptionStatusEnum.Active,
34
+ enyo_subscription_js_1.EnyoSubscriptionStatusEnum.CancelledUntilPeriodEnd,
35
+ enyo_subscription_js_1.EnyoSubscriptionStatusEnum.Grace,
36
+ ];
37
+ /**
38
+ * Identity helper that type-checks a subscription plan at definition time
39
+ * (mirrors `defineEnergyAppPackage`). Prefer this over a bare object literal so
40
+ * mistakes surface where the plan is written.
41
+ *
42
+ * @param plan - The subscription plan to declare.
43
+ * @returns The same plan, typed as {@link EnergyAppPackageSubscriptionPlan}.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * defineSubscriptionPlan({
48
+ * planKey: 'premium',
49
+ * paymentInterval: EnyoSubscriptionPaymentIntervalEnum.Yearly,
50
+ * prices: [{amountWithoutTax: 41.18, currency: EnyoCurrencyEnum.EUR}],
51
+ * storeEntry: [
52
+ * {language: 'en', title: 'Premium', shortDescription: 'Optimises your heat pump with dynamic prices.'},
53
+ * {language: 'de', title: 'Premium', shortDescription: 'Optimiert deine Wärmepumpe mit dynamischen Preisen.'}
54
+ * ]
55
+ * })
56
+ * ```
57
+ */
58
+ function defineSubscriptionPlan(plan) {
59
+ return plan;
60
+ }
61
+ /**
62
+ * Tests whether a subscription currently grants access, i.e. whether its
63
+ * status is one of {@link ACTIVE_SUBSCRIPTION_STATUSES}.
64
+ *
65
+ * @param subscription - The subscription to check.
66
+ * @returns True when the customer may use what the subscription's plan unlocks.
67
+ */
68
+ function isSubscriptionActive(subscription) {
69
+ return exports.ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status);
70
+ }
71
+ /**
72
+ * Tests whether the customer has an active subscription for a plan.
73
+ *
74
+ * @param subscriptions - The subscriptions, as returned by
75
+ * {@link EnergyAppSubscriptions.getSubscriptions}.
76
+ * @param planKey - The {@link EnergyAppPackageSubscriptionPlan.planKey} to check.
77
+ * @returns True when at least one subscription for the plan is active.
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const subscriptions = await energyApp.useSubscriptions().getSubscriptions();
82
+ * if (isPlanActive(subscriptions, 'premium')) {
83
+ * enableOptimisation();
84
+ * }
85
+ * ```
86
+ */
87
+ function isPlanActive(subscriptions, planKey) {
88
+ return subscriptions.some(subscription => subscription.planKey === planKey && isSubscriptionActive(subscription));
89
+ }
90
+ /**
91
+ * Tests whether two units are the same unit, i.e. have the same `type` and
92
+ * `referenceId`. The name of a custom unit is not compared.
93
+ *
94
+ * @param a - The first unit.
95
+ * @param b - The second unit.
96
+ * @returns True when both identify the same unit.
97
+ */
98
+ function isSameSubscriptionUnit(a, b) {
99
+ return a.type === b.type && a.referenceId === b.referenceId;
100
+ }
101
+ /**
102
+ * Tests whether one unit of a `per-unit` plan is paid for, i.e. whether an
103
+ * active subscription for the plan lists the unit in
104
+ * {@link EnyoSubscription.units}.
105
+ *
106
+ * @param subscriptions - The subscriptions, as returned by
107
+ * {@link EnergyAppSubscriptions.getSubscriptions}.
108
+ * @param planKey - The {@link EnergyAppPackageSubscriptionPlan.planKey} to check.
109
+ * @param unit - The unit to check, identified by `type` and `referenceId`.
110
+ * @returns True when the unit is covered by an active subscription for the plan.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const subscriptions = await energyApp.useSubscriptions().getSubscriptions();
115
+ * if (isUnitActive(subscriptions, 'smart-charging', {type: 'appliance', referenceId: wallbox.id})) {
116
+ * enableSmartCharging(wallbox);
117
+ * }
118
+ * ```
119
+ */
120
+ function isUnitActive(subscriptions, planKey, unit) {
121
+ return subscriptions.some(subscription => subscription.planKey === planKey
122
+ && isSubscriptionActive(subscription)
123
+ && (subscription.units ?? []).some(paid => isSameSubscriptionUnit(paid, unit)));
124
+ }