alchemy 0.15.6 → 0.15.7

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.
@@ -1,4 +1,5 @@
1
1
  export * from "./price.js";
2
2
  export * from "./product.js";
3
3
  export * from "./webhook.js";
4
+ export * from "./meter.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/stripe/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/stripe/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from "./price.js";
2
2
  export * from "./product.js";
3
3
  export * from "./webhook.js";
4
+ export * from "./meter.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/stripe/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/stripe/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,137 @@
1
+ import Stripe from "stripe";
2
+ import type { Context } from "../context.js";
3
+ import { Resource } from "../resource.js";
4
+ /**
5
+ * Properties for creating or updating a Stripe Meter.
6
+ * Note: Nested objects like defaultAggregation, customerMapping, valueSettings
7
+ * use camelCase for keys here (e.g., eventPayloadKey), and the resource
8
+ * implementation will map them to snake_case for Stripe API calls.
9
+ */
10
+ export interface MeterProps {
11
+ displayName: string;
12
+ eventName: string;
13
+ status?: "active" | "inactive";
14
+ defaultAggregation: {
15
+ formula: Stripe.Billing.MeterCreateParams.DefaultAggregation.Formula;
16
+ };
17
+ customerMapping: {
18
+ eventPayloadKey: string;
19
+ type: Stripe.Billing.MeterCreateParams.CustomerMapping["type"];
20
+ };
21
+ valueSettings: {
22
+ eventPayloadKey: string;
23
+ };
24
+ }
25
+ /**
26
+ * Output returned after Stripe Meter creation/update.
27
+ */
28
+ export interface Meter extends Resource<"stripe::Meter"> {
29
+ id: string;
30
+ object: "billing.meter";
31
+ displayName: string;
32
+ eventName: string;
33
+ defaultAggregation: {
34
+ formula: Stripe.Billing.Meter.DefaultAggregation["formula"];
35
+ };
36
+ customerMapping: {
37
+ eventPayloadKey: string;
38
+ type: Stripe.Billing.Meter.CustomerMapping["type"];
39
+ };
40
+ valueSettings: {
41
+ eventPayloadKey: string;
42
+ };
43
+ status: "active" | "inactive";
44
+ createdAt: number;
45
+ updatedAt: number;
46
+ livemode: boolean;
47
+ statusTransitions?: {
48
+ deactivatedAt: number | null;
49
+ };
50
+ }
51
+ /**
52
+ * Manages Stripe Billing Meters. Meters allow you to define how usage of a product
53
+ * is measured and aggregated for billing purposes. You can track events and their
54
+ * values, and Stripe will calculate the usage based on the meter's configuration.
55
+ *
56
+ * Stripe Meters have several properties that are immutable after creation:
57
+ * `displayName`, `eventName`, `defaultAggregation`, `customerMapping`, and
58
+ * `valueSettings`. Attempting to update these will result in an error.
59
+ * The `status` of a meter (active/inactive) can be updated.
60
+ *
61
+ * @example
62
+ * // Create a new active meter to track API calls, summing the 'count' field from events.
63
+ * const apiCallMeter = await Meter("apiUsageMeter", {
64
+ * displayName: "API Call Usage",
65
+ * eventName: "api.call.recorded", // The event name Stripe will listen for
66
+ * defaultAggregation: {
67
+ * formula: "sum" // Sum up the values of events
68
+ * },
69
+ * customerMapping: {
70
+ * eventPayloadKey: "customer_id", // Field in the event payload that identifies the Stripe Customer
71
+ * type: "by_id"
72
+ * },
73
+ * valueSettings: {
74
+ * eventPayloadKey: "count" // Field in the event payload that represents the value to aggregate
75
+ * }
76
+ * // status defaults to 'active' if not specified
77
+ * });
78
+ *
79
+ * @example
80
+ * // Create a new meter for data storage, initially inactive.
81
+ * // This meter will take the last reported 'gb_used' value within a billing period.
82
+ * const dataStorageMeterInactive = await Meter("dataStorageMeter", {
83
+ * displayName: "Data Storage GB",
84
+ * eventName: "data.storage.reported",
85
+ * status: "inactive", // Explicitly set to inactive
86
+ * defaultAggregation: {
87
+ * formula: "last_during_period"
88
+ * },
89
+ * customerMapping: {
90
+ * eventPayloadKey: "user_stripe_id",
91
+ * type: "by_id"
92
+ * },
93
+ * valueSettings: {
94
+ * eventPayloadKey: "gb_used"
95
+ * }
96
+ * });
97
+ *
98
+ * @example
99
+ * // (Scenario: Assuming 'dataStorageMeterInactive' from the previous example was created
100
+ * // and its ID is available, e.g., via `dataStorageMeterInactive.id`) *
101
+ * // If dataStorageMeterInactive was previously defined and we want to activate it:
102
+ * const activatedStorageMeter = await Meter("dataStorageMeter", { // Same logicalId
103
+ * displayName: "Data Storage GB", // Immutable, must match existing
104
+ * eventName: "data.storage.reported", // Immutable, must match existing
105
+ * status: "active", // Update: change status to active
106
+ * defaultAggregation: { // Immutable, must match existing
107
+ * formula: "last_during_period"
108
+ * },
109
+ * customerMapping: { // Immutable, must match existing
110
+ * eventPayloadKey: "user_stripe_id",
111
+ * type: "by_id"
112
+ * },
113
+ * valueSettings: { // Immutable, must match existing
114
+ * eventPayloadKey: "gb_used"
115
+ * }
116
+ * });
117
+ *
118
+ * @example
119
+ * // To deactivate an existing active meter (e.g. 'apiCallMeter' from first example):
120
+ * const deactivatedApiMeter = await Meter("apiUsageMeter", { // Same logicalId
121
+ * displayName: "API Call Usage", // Immutable, must match existing
122
+ * eventName: "api.call.recorded", // Immutable, must match existing
123
+ * status: "inactive", // Update: change status to inactive
124
+ * defaultAggregation: { // Immutable, must match existing
125
+ * formula: "sum"
126
+ * },
127
+ * customerMapping: { // Immutable, must match existing
128
+ * eventPayloadKey: "customer_id",
129
+ * type: "by_id"
130
+ * },
131
+ * valueSettings: { // Immutable, must match existing
132
+ * eventPayloadKey: "count"
133
+ * }
134
+ * });
135
+ */
136
+ export declare const Meter: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Meter>, logicalId: string, props: MeterProps) => Promise<Meter>);
137
+ //# sourceMappingURL=meter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meter.d.ts","sourceRoot":"","sources":["../../src/stripe/meter.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC/B,kBAAkB,EAAE;QAClB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,OAAO,CAAC;KACtE,CAAC;IACF,eAAe,EAAE;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;KAChE,CAAC;IACF,aAAa,EAAE;QACb,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,KAAM,SAAQ,QAAQ,CAAC,eAAe,CAAC;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,eAAe,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE;QAClB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;KAC7D,CAAC;IACF,eAAe,EAAE;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;KACpD,CAAC;IACF,aAAa,EAAE;QACb,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE;QAClB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AAEH,eAAO,MAAM,KAAK,yFAGR,OAAO,CAAC,KAAK,CAAC,aACT,MAAM,SACV,UAAU,KAChB,OAAO,CAAC,KAAK,CAAC,CAwKlB,CAAC"}
@@ -0,0 +1,226 @@
1
+ import Stripe from "stripe";
2
+ import { Resource } from "../resource.js";
3
+ /**
4
+ * Manages Stripe Billing Meters. Meters allow you to define how usage of a product
5
+ * is measured and aggregated for billing purposes. You can track events and their
6
+ * values, and Stripe will calculate the usage based on the meter's configuration.
7
+ *
8
+ * Stripe Meters have several properties that are immutable after creation:
9
+ * `displayName`, `eventName`, `defaultAggregation`, `customerMapping`, and
10
+ * `valueSettings`. Attempting to update these will result in an error.
11
+ * The `status` of a meter (active/inactive) can be updated.
12
+ *
13
+ * @example
14
+ * // Create a new active meter to track API calls, summing the 'count' field from events.
15
+ * const apiCallMeter = await Meter("apiUsageMeter", {
16
+ * displayName: "API Call Usage",
17
+ * eventName: "api.call.recorded", // The event name Stripe will listen for
18
+ * defaultAggregation: {
19
+ * formula: "sum" // Sum up the values of events
20
+ * },
21
+ * customerMapping: {
22
+ * eventPayloadKey: "customer_id", // Field in the event payload that identifies the Stripe Customer
23
+ * type: "by_id"
24
+ * },
25
+ * valueSettings: {
26
+ * eventPayloadKey: "count" // Field in the event payload that represents the value to aggregate
27
+ * }
28
+ * // status defaults to 'active' if not specified
29
+ * });
30
+ *
31
+ * @example
32
+ * // Create a new meter for data storage, initially inactive.
33
+ * // This meter will take the last reported 'gb_used' value within a billing period.
34
+ * const dataStorageMeterInactive = await Meter("dataStorageMeter", {
35
+ * displayName: "Data Storage GB",
36
+ * eventName: "data.storage.reported",
37
+ * status: "inactive", // Explicitly set to inactive
38
+ * defaultAggregation: {
39
+ * formula: "last_during_period"
40
+ * },
41
+ * customerMapping: {
42
+ * eventPayloadKey: "user_stripe_id",
43
+ * type: "by_id"
44
+ * },
45
+ * valueSettings: {
46
+ * eventPayloadKey: "gb_used"
47
+ * }
48
+ * });
49
+ *
50
+ * @example
51
+ * // (Scenario: Assuming 'dataStorageMeterInactive' from the previous example was created
52
+ * // and its ID is available, e.g., via `dataStorageMeterInactive.id`) *
53
+ * // If dataStorageMeterInactive was previously defined and we want to activate it:
54
+ * const activatedStorageMeter = await Meter("dataStorageMeter", { // Same logicalId
55
+ * displayName: "Data Storage GB", // Immutable, must match existing
56
+ * eventName: "data.storage.reported", // Immutable, must match existing
57
+ * status: "active", // Update: change status to active
58
+ * defaultAggregation: { // Immutable, must match existing
59
+ * formula: "last_during_period"
60
+ * },
61
+ * customerMapping: { // Immutable, must match existing
62
+ * eventPayloadKey: "user_stripe_id",
63
+ * type: "by_id"
64
+ * },
65
+ * valueSettings: { // Immutable, must match existing
66
+ * eventPayloadKey: "gb_used"
67
+ * }
68
+ * });
69
+ *
70
+ * @example
71
+ * // To deactivate an existing active meter (e.g. 'apiCallMeter' from first example):
72
+ * const deactivatedApiMeter = await Meter("apiUsageMeter", { // Same logicalId
73
+ * displayName: "API Call Usage", // Immutable, must match existing
74
+ * eventName: "api.call.recorded", // Immutable, must match existing
75
+ * status: "inactive", // Update: change status to inactive
76
+ * defaultAggregation: { // Immutable, must match existing
77
+ * formula: "sum"
78
+ * },
79
+ * customerMapping: { // Immutable, must match existing
80
+ * eventPayloadKey: "customer_id",
81
+ * type: "by_id"
82
+ * },
83
+ * valueSettings: { // Immutable, must match existing
84
+ * eventPayloadKey: "count"
85
+ * }
86
+ * });
87
+ */
88
+ export const Meter = Resource("stripe::Meter", async function (logicalId, props) {
89
+ const apiKey = process.env.STRIPE_API_KEY;
90
+ if (!apiKey) {
91
+ throw new Error("STRIPE_API_KEY environment variable is required");
92
+ }
93
+ const stripe = new Stripe(apiKey);
94
+ const currentOutputId = this.output?.id;
95
+ // Helper to map MeterProps (camelCase nested) to Stripe API params (snake_case nested)
96
+ const mapPropsToStripeParams = (inputProps) => ({
97
+ display_name: inputProps.displayName,
98
+ event_name: inputProps.eventName,
99
+ default_aggregation: {
100
+ formula: inputProps.defaultAggregation.formula,
101
+ },
102
+ customer_mapping: {
103
+ event_payload_key: inputProps.customerMapping.eventPayloadKey,
104
+ type: inputProps.customerMapping.type,
105
+ },
106
+ value_settings: {
107
+ event_payload_key: inputProps.valueSettings.eventPayloadKey,
108
+ },
109
+ });
110
+ // Helper to map Stripe API response (snake_case) to Meter output interface (camelCase)
111
+ const mapStripeObjectToMeterOutput = (stripeMeter) => ({
112
+ id: stripeMeter.id,
113
+ object: stripeMeter.object,
114
+ displayName: stripeMeter.display_name,
115
+ eventName: stripeMeter.event_name,
116
+ defaultAggregation: {
117
+ formula: stripeMeter.default_aggregation.formula,
118
+ },
119
+ customerMapping: {
120
+ eventPayloadKey: stripeMeter.customer_mapping.event_payload_key,
121
+ type: stripeMeter.customer_mapping.type,
122
+ },
123
+ valueSettings: {
124
+ eventPayloadKey: stripeMeter.value_settings.event_payload_key,
125
+ },
126
+ status: stripeMeter.status,
127
+ createdAt: stripeMeter.created,
128
+ updatedAt: stripeMeter.updated,
129
+ livemode: stripeMeter.livemode,
130
+ statusTransitions: stripeMeter.status_transitions
131
+ ? {
132
+ deactivatedAt: stripeMeter.status_transitions.deactivated_at,
133
+ }
134
+ : undefined,
135
+ });
136
+ if (this.phase === "delete") {
137
+ if (currentOutputId) {
138
+ try {
139
+ const meter = await stripe.billing.meters.retrieve(currentOutputId);
140
+ if (meter.status === "active") {
141
+ await stripe.billing.meters.deactivate(currentOutputId);
142
+ }
143
+ }
144
+ catch (error) {
145
+ if (error?.code !== "resource_missing") {
146
+ throw error;
147
+ }
148
+ }
149
+ }
150
+ return this.destroy();
151
+ }
152
+ // --- Create or Update Phase ---
153
+ let stripeAPIResponse;
154
+ if (this.phase === "update" && currentOutputId) {
155
+ const existingStripeMeter = await stripe.billing.meters.retrieve(currentOutputId);
156
+ // Normalize existingMeter's relevant fields for immutable comparison
157
+ const existingPropsForCompare = {
158
+ displayName: existingStripeMeter.display_name,
159
+ eventName: existingStripeMeter.event_name,
160
+ defaultAggregation: {
161
+ formula: existingStripeMeter.default_aggregation.formula,
162
+ },
163
+ customerMapping: {
164
+ eventPayloadKey: existingStripeMeter.customer_mapping.event_payload_key,
165
+ type: existingStripeMeter.customer_mapping.type,
166
+ },
167
+ valueSettings: {
168
+ eventPayloadKey: existingStripeMeter.value_settings.event_payload_key,
169
+ },
170
+ };
171
+ // Check for immutable property changes (comparing against props passed to resource)
172
+ if (props.displayName !== existingPropsForCompare.displayName ||
173
+ props.eventName !== existingPropsForCompare.eventName ||
174
+ JSON.stringify(props.defaultAggregation) !==
175
+ JSON.stringify(existingPropsForCompare.defaultAggregation) ||
176
+ JSON.stringify(props.customerMapping) !==
177
+ JSON.stringify(existingPropsForCompare.customerMapping) ||
178
+ JSON.stringify(props.valueSettings) !==
179
+ JSON.stringify(existingPropsForCompare.valueSettings)) {
180
+ throw new Error(`Attempted to update immutable properties for Stripe Meter ${currentOutputId}. ` +
181
+ "displayName, eventName, defaultAggregation, customerMapping, and valueSettings cannot be changed.");
182
+ }
183
+ // Handle status change
184
+ if (props.status && props.status !== existingStripeMeter.status) {
185
+ if (props.status === "inactive" &&
186
+ existingStripeMeter.status === "active") {
187
+ stripeAPIResponse =
188
+ await stripe.billing.meters.deactivate(currentOutputId);
189
+ }
190
+ else if (props.status === "active" &&
191
+ existingStripeMeter.status === "inactive") {
192
+ console.log(`Reactivating Stripe Meter ${currentOutputId}.`);
193
+ stripeAPIResponse =
194
+ await stripe.billing.meters.reactivate(currentOutputId);
195
+ }
196
+ else {
197
+ stripeAPIResponse = existingStripeMeter; // No change in status
198
+ }
199
+ }
200
+ else {
201
+ stripeAPIResponse = existingStripeMeter; // No status change requested
202
+ }
203
+ }
204
+ else {
205
+ // Create phase
206
+ if (!props.displayName ||
207
+ !props.eventName ||
208
+ !props.defaultAggregation ||
209
+ !props.customerMapping ||
210
+ !props.valueSettings) {
211
+ throw new Error("displayName, eventName, defaultAggregation, customerMapping, and valueSettings are required for creating a Stripe Meter.");
212
+ }
213
+ const createParams = mapPropsToStripeParams(props);
214
+ stripeAPIResponse = await stripe.billing.meters.create(createParams);
215
+ // If status 'inactive' is requested during creation, and Stripe created it as 'active'
216
+ if (props.status === "inactive" &&
217
+ stripeAPIResponse.status === "active") {
218
+ stripeAPIResponse = await stripe.billing.meters.deactivate(stripeAPIResponse.id);
219
+ }
220
+ else if (props.status && props.status !== stripeAPIResponse.status) {
221
+ console.warn(`Meter ${stripeAPIResponse.id} created with status ${stripeAPIResponse.status} but requested ${props.status}. Ensure this is intended.`);
222
+ }
223
+ }
224
+ return this(mapStripeObjectToMeterOutput(stripeAPIResponse));
225
+ });
226
+ //# sourceMappingURL=meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meter.js","sourceRoot":"","sources":["../../src/stripe/meter.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.15.6",
3
+ "version": "0.15.7",
4
4
  "type": "module",
5
5
  "module": "./lib/index.js",
6
6
  "scripts": {
@@ -1,3 +1,4 @@
1
1
  export * from "./price.js";
2
2
  export * from "./product.js";
3
3
  export * from "./webhook.js";
4
+ export * from "./meter.js";
@@ -0,0 +1,314 @@
1
+ import Stripe from "stripe";
2
+ import type { Context } from "../context.js";
3
+ import { Resource } from "../resource.js";
4
+
5
+ /**
6
+ * Properties for creating or updating a Stripe Meter.
7
+ * Note: Nested objects like defaultAggregation, customerMapping, valueSettings
8
+ * use camelCase for keys here (e.g., eventPayloadKey), and the resource
9
+ * implementation will map them to snake_case for Stripe API calls.
10
+ */
11
+ export interface MeterProps {
12
+ displayName: string;
13
+ eventName: string;
14
+ status?: "active" | "inactive"; // 'active' is default on create by Stripe
15
+ defaultAggregation: {
16
+ formula: Stripe.Billing.MeterCreateParams.DefaultAggregation.Formula;
17
+ };
18
+ customerMapping: {
19
+ eventPayloadKey: string;
20
+ type: Stripe.Billing.MeterCreateParams.CustomerMapping["type"];
21
+ };
22
+ valueSettings: {
23
+ eventPayloadKey: string;
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Output returned after Stripe Meter creation/update.
29
+ */
30
+ export interface Meter extends Resource<"stripe::Meter"> {
31
+ id: string;
32
+ object: "billing.meter";
33
+ displayName: string;
34
+ eventName: string;
35
+ defaultAggregation: {
36
+ formula: Stripe.Billing.Meter.DefaultAggregation["formula"];
37
+ };
38
+ customerMapping: {
39
+ eventPayloadKey: string;
40
+ type: Stripe.Billing.Meter.CustomerMapping["type"];
41
+ };
42
+ valueSettings: {
43
+ eventPayloadKey: string;
44
+ };
45
+ status: "active" | "inactive";
46
+ createdAt: number; // Unix timestamp
47
+ updatedAt: number; // Unix timestamp
48
+ livemode: boolean;
49
+ statusTransitions?: {
50
+ deactivatedAt: number | null; // Unix timestamp
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Manages Stripe Billing Meters. Meters allow you to define how usage of a product
56
+ * is measured and aggregated for billing purposes. You can track events and their
57
+ * values, and Stripe will calculate the usage based on the meter's configuration.
58
+ *
59
+ * Stripe Meters have several properties that are immutable after creation:
60
+ * `displayName`, `eventName`, `defaultAggregation`, `customerMapping`, and
61
+ * `valueSettings`. Attempting to update these will result in an error.
62
+ * The `status` of a meter (active/inactive) can be updated.
63
+ *
64
+ * @example
65
+ * // Create a new active meter to track API calls, summing the 'count' field from events.
66
+ * const apiCallMeter = await Meter("apiUsageMeter", {
67
+ * displayName: "API Call Usage",
68
+ * eventName: "api.call.recorded", // The event name Stripe will listen for
69
+ * defaultAggregation: {
70
+ * formula: "sum" // Sum up the values of events
71
+ * },
72
+ * customerMapping: {
73
+ * eventPayloadKey: "customer_id", // Field in the event payload that identifies the Stripe Customer
74
+ * type: "by_id"
75
+ * },
76
+ * valueSettings: {
77
+ * eventPayloadKey: "count" // Field in the event payload that represents the value to aggregate
78
+ * }
79
+ * // status defaults to 'active' if not specified
80
+ * });
81
+ *
82
+ * @example
83
+ * // Create a new meter for data storage, initially inactive.
84
+ * // This meter will take the last reported 'gb_used' value within a billing period.
85
+ * const dataStorageMeterInactive = await Meter("dataStorageMeter", {
86
+ * displayName: "Data Storage GB",
87
+ * eventName: "data.storage.reported",
88
+ * status: "inactive", // Explicitly set to inactive
89
+ * defaultAggregation: {
90
+ * formula: "last_during_period"
91
+ * },
92
+ * customerMapping: {
93
+ * eventPayloadKey: "user_stripe_id",
94
+ * type: "by_id"
95
+ * },
96
+ * valueSettings: {
97
+ * eventPayloadKey: "gb_used"
98
+ * }
99
+ * });
100
+ *
101
+ * @example
102
+ * // (Scenario: Assuming 'dataStorageMeterInactive' from the previous example was created
103
+ * // and its ID is available, e.g., via `dataStorageMeterInactive.id`) *
104
+ * // If dataStorageMeterInactive was previously defined and we want to activate it:
105
+ * const activatedStorageMeter = await Meter("dataStorageMeter", { // Same logicalId
106
+ * displayName: "Data Storage GB", // Immutable, must match existing
107
+ * eventName: "data.storage.reported", // Immutable, must match existing
108
+ * status: "active", // Update: change status to active
109
+ * defaultAggregation: { // Immutable, must match existing
110
+ * formula: "last_during_period"
111
+ * },
112
+ * customerMapping: { // Immutable, must match existing
113
+ * eventPayloadKey: "user_stripe_id",
114
+ * type: "by_id"
115
+ * },
116
+ * valueSettings: { // Immutable, must match existing
117
+ * eventPayloadKey: "gb_used"
118
+ * }
119
+ * });
120
+ *
121
+ * @example
122
+ * // To deactivate an existing active meter (e.g. 'apiCallMeter' from first example):
123
+ * const deactivatedApiMeter = await Meter("apiUsageMeter", { // Same logicalId
124
+ * displayName: "API Call Usage", // Immutable, must match existing
125
+ * eventName: "api.call.recorded", // Immutable, must match existing
126
+ * status: "inactive", // Update: change status to inactive
127
+ * defaultAggregation: { // Immutable, must match existing
128
+ * formula: "sum"
129
+ * },
130
+ * customerMapping: { // Immutable, must match existing
131
+ * eventPayloadKey: "customer_id",
132
+ * type: "by_id"
133
+ * },
134
+ * valueSettings: { // Immutable, must match existing
135
+ * eventPayloadKey: "count"
136
+ * }
137
+ * });
138
+ */
139
+
140
+ export const Meter = Resource(
141
+ "stripe::Meter",
142
+ async function (
143
+ this: Context<Meter>,
144
+ logicalId: string,
145
+ props: MeterProps,
146
+ ): Promise<Meter> {
147
+ const apiKey = process.env.STRIPE_API_KEY;
148
+ if (!apiKey) {
149
+ throw new Error("STRIPE_API_KEY environment variable is required");
150
+ }
151
+ const stripe = new Stripe(apiKey);
152
+
153
+ const currentOutputId = this.output?.id;
154
+
155
+ // Helper to map MeterProps (camelCase nested) to Stripe API params (snake_case nested)
156
+ const mapPropsToStripeParams = (
157
+ inputProps: MeterProps,
158
+ ): Stripe.Billing.MeterCreateParams => ({
159
+ display_name: inputProps.displayName,
160
+ event_name: inputProps.eventName,
161
+ default_aggregation: {
162
+ formula: inputProps.defaultAggregation.formula,
163
+ },
164
+ customer_mapping: {
165
+ event_payload_key: inputProps.customerMapping.eventPayloadKey,
166
+ type: inputProps.customerMapping.type,
167
+ },
168
+ value_settings: {
169
+ event_payload_key: inputProps.valueSettings.eventPayloadKey,
170
+ },
171
+ });
172
+
173
+ // Helper to map Stripe API response (snake_case) to Meter output interface (camelCase)
174
+ const mapStripeObjectToMeterOutput = (
175
+ stripeMeter: Stripe.Billing.Meter,
176
+ ): Omit<Meter, keyof Resource<"stripe::Meter">> => ({
177
+ id: stripeMeter.id,
178
+ object: stripeMeter.object,
179
+ displayName: stripeMeter.display_name,
180
+ eventName: stripeMeter.event_name,
181
+ defaultAggregation: {
182
+ formula: stripeMeter.default_aggregation.formula,
183
+ },
184
+ customerMapping: {
185
+ eventPayloadKey: stripeMeter.customer_mapping.event_payload_key,
186
+ type: stripeMeter.customer_mapping.type,
187
+ },
188
+ valueSettings: {
189
+ eventPayloadKey: stripeMeter.value_settings.event_payload_key,
190
+ },
191
+ status: stripeMeter.status as "active" | "inactive",
192
+ createdAt: stripeMeter.created,
193
+ updatedAt: stripeMeter.updated,
194
+ livemode: stripeMeter.livemode,
195
+ statusTransitions: stripeMeter.status_transitions
196
+ ? {
197
+ deactivatedAt: stripeMeter.status_transitions.deactivated_at,
198
+ }
199
+ : undefined,
200
+ });
201
+
202
+ if (this.phase === "delete") {
203
+ if (currentOutputId) {
204
+ try {
205
+ const meter = await stripe.billing.meters.retrieve(currentOutputId);
206
+ if (meter.status === "active") {
207
+ await stripe.billing.meters.deactivate(currentOutputId);
208
+ }
209
+ } catch (error: any) {
210
+ if (error?.code !== "resource_missing") {
211
+ throw error;
212
+ }
213
+ }
214
+ }
215
+ return this.destroy();
216
+ }
217
+
218
+ // --- Create or Update Phase ---
219
+ let stripeAPIResponse: Stripe.Billing.Meter;
220
+
221
+ if (this.phase === "update" && currentOutputId) {
222
+ const existingStripeMeter =
223
+ await stripe.billing.meters.retrieve(currentOutputId);
224
+
225
+ // Normalize existingMeter's relevant fields for immutable comparison
226
+ const existingPropsForCompare = {
227
+ displayName: existingStripeMeter.display_name,
228
+ eventName: existingStripeMeter.event_name,
229
+ defaultAggregation: {
230
+ formula: existingStripeMeter.default_aggregation.formula,
231
+ },
232
+ customerMapping: {
233
+ eventPayloadKey:
234
+ existingStripeMeter.customer_mapping.event_payload_key,
235
+ type: existingStripeMeter.customer_mapping.type,
236
+ },
237
+ valueSettings: {
238
+ eventPayloadKey: existingStripeMeter.value_settings.event_payload_key,
239
+ },
240
+ };
241
+
242
+ // Check for immutable property changes (comparing against props passed to resource)
243
+ if (
244
+ props.displayName !== existingPropsForCompare.displayName ||
245
+ props.eventName !== existingPropsForCompare.eventName ||
246
+ JSON.stringify(props.defaultAggregation) !==
247
+ JSON.stringify(existingPropsForCompare.defaultAggregation) ||
248
+ JSON.stringify(props.customerMapping) !==
249
+ JSON.stringify(existingPropsForCompare.customerMapping) ||
250
+ JSON.stringify(props.valueSettings) !==
251
+ JSON.stringify(existingPropsForCompare.valueSettings)
252
+ ) {
253
+ throw new Error(
254
+ `Attempted to update immutable properties for Stripe Meter ${currentOutputId}. ` +
255
+ "displayName, eventName, defaultAggregation, customerMapping, and valueSettings cannot be changed.",
256
+ );
257
+ }
258
+
259
+ // Handle status change
260
+ if (props.status && props.status !== existingStripeMeter.status) {
261
+ if (
262
+ props.status === "inactive" &&
263
+ existingStripeMeter.status === "active"
264
+ ) {
265
+ stripeAPIResponse =
266
+ await stripe.billing.meters.deactivate(currentOutputId);
267
+ } else if (
268
+ props.status === "active" &&
269
+ existingStripeMeter.status === "inactive"
270
+ ) {
271
+ console.log(`Reactivating Stripe Meter ${currentOutputId}.`);
272
+ stripeAPIResponse =
273
+ await stripe.billing.meters.reactivate(currentOutputId);
274
+ } else {
275
+ stripeAPIResponse = existingStripeMeter; // No change in status
276
+ }
277
+ } else {
278
+ stripeAPIResponse = existingStripeMeter; // No status change requested
279
+ }
280
+ } else {
281
+ // Create phase
282
+ if (
283
+ !props.displayName ||
284
+ !props.eventName ||
285
+ !props.defaultAggregation ||
286
+ !props.customerMapping ||
287
+ !props.valueSettings
288
+ ) {
289
+ throw new Error(
290
+ "displayName, eventName, defaultAggregation, customerMapping, and valueSettings are required for creating a Stripe Meter.",
291
+ );
292
+ }
293
+
294
+ const createParams = mapPropsToStripeParams(props);
295
+ stripeAPIResponse = await stripe.billing.meters.create(createParams);
296
+
297
+ // If status 'inactive' is requested during creation, and Stripe created it as 'active'
298
+ if (
299
+ props.status === "inactive" &&
300
+ stripeAPIResponse.status === "active"
301
+ ) {
302
+ stripeAPIResponse = await stripe.billing.meters.deactivate(
303
+ stripeAPIResponse.id,
304
+ );
305
+ } else if (props.status && props.status !== stripeAPIResponse.status) {
306
+ console.warn(
307
+ `Meter ${stripeAPIResponse.id} created with status ${stripeAPIResponse.status} but requested ${props.status}. Ensure this is intended.`,
308
+ );
309
+ }
310
+ }
311
+
312
+ return this(mapStripeObjectToMeterOutput(stripeAPIResponse));
313
+ },
314
+ );