alchemy 0.24.0 → 0.25.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 (184) hide show
  1. package/lib/aws/control/client.js +1 -1
  2. package/lib/aws/control/client.js.map +1 -1
  3. package/lib/aws/control/resource.d.ts.map +1 -1
  4. package/lib/aws/control/resource.js +2 -1
  5. package/lib/aws/control/resource.js.map +1 -1
  6. package/lib/aws/function.d.ts.map +1 -1
  7. package/lib/aws/function.js +1 -4
  8. package/lib/aws/function.js.map +1 -1
  9. package/lib/cloudflare/ai.d.ts +21 -0
  10. package/lib/cloudflare/ai.d.ts.map +1 -1
  11. package/lib/cloudflare/ai.js +21 -0
  12. package/lib/cloudflare/ai.js.map +1 -1
  13. package/lib/cloudflare/bindings.d.ts +3 -3
  14. package/lib/cloudflare/bindings.d.ts.map +1 -1
  15. package/lib/cloudflare/bindings.js.map +1 -1
  16. package/lib/cloudflare/bound.d.ts +3 -3
  17. package/lib/cloudflare/bound.d.ts.map +1 -1
  18. package/lib/cloudflare/do-state-store/index.d.ts +2 -0
  19. package/lib/cloudflare/do-state-store/index.d.ts.map +1 -0
  20. package/lib/cloudflare/do-state-store/index.js +2 -0
  21. package/lib/cloudflare/do-state-store/index.js.map +1 -0
  22. package/lib/cloudflare/do-state-store/internal.d.ts +19 -0
  23. package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -0
  24. package/lib/cloudflare/do-state-store/internal.js +163 -0
  25. package/lib/cloudflare/do-state-store/internal.js.map +1 -0
  26. package/lib/cloudflare/do-state-store/store.d.ts +59 -0
  27. package/lib/cloudflare/do-state-store/store.d.ts.map +1 -0
  28. package/lib/cloudflare/do-state-store/store.js +120 -0
  29. package/lib/cloudflare/do-state-store/store.js.map +1 -0
  30. package/lib/cloudflare/do-state-store/types.d.ts +82 -0
  31. package/lib/cloudflare/do-state-store/types.d.ts.map +1 -0
  32. package/lib/cloudflare/do-state-store/types.js +1 -0
  33. package/lib/cloudflare/do-state-store/types.js.map +1 -0
  34. package/lib/cloudflare/do-state-store/worker.d.ts +27 -0
  35. package/lib/cloudflare/do-state-store/worker.d.ts.map +1 -0
  36. package/lib/cloudflare/do-state-store/worker.js +170 -0
  37. package/lib/cloudflare/do-state-store/worker.js.map +1 -0
  38. package/lib/cloudflare/index.d.ts +1 -0
  39. package/lib/cloudflare/index.d.ts.map +1 -1
  40. package/lib/cloudflare/index.js +1 -0
  41. package/lib/cloudflare/index.js.map +1 -1
  42. package/lib/cloudflare/pipeline.js +1 -1
  43. package/lib/cloudflare/pipeline.js.map +1 -1
  44. package/lib/cloudflare/queue-consumer.d.ts +14 -1
  45. package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
  46. package/lib/cloudflare/queue-consumer.js +17 -0
  47. package/lib/cloudflare/queue-consumer.js.map +1 -1
  48. package/lib/cloudflare/website.d.ts +30 -1
  49. package/lib/cloudflare/website.d.ts.map +1 -1
  50. package/lib/cloudflare/website.js +8 -5
  51. package/lib/cloudflare/website.js.map +1 -1
  52. package/lib/cloudflare/worker-assets.d.ts.map +1 -1
  53. package/lib/cloudflare/worker-assets.js +13 -3
  54. package/lib/cloudflare/worker-assets.js.map +1 -1
  55. package/lib/cloudflare/worker-metadata.js +1 -1
  56. package/lib/cloudflare/worker-metadata.js.map +1 -1
  57. package/lib/cloudflare/worker.d.ts +40 -1
  58. package/lib/cloudflare/worker.d.ts.map +1 -1
  59. package/lib/cloudflare/worker.js +40 -38
  60. package/lib/cloudflare/worker.js.map +1 -1
  61. package/lib/cloudflare/wrangler.json.js +1 -1
  62. package/lib/cloudflare/wrangler.json.js.map +1 -1
  63. package/lib/cloudflare/zone.d.ts +30 -2
  64. package/lib/cloudflare/zone.d.ts.map +1 -1
  65. package/lib/cloudflare/zone.js +53 -0
  66. package/lib/cloudflare/zone.js.map +1 -1
  67. package/lib/fs/file-system-state-store.d.ts +3 -1
  68. package/lib/fs/file-system-state-store.d.ts.map +1 -1
  69. package/lib/fs/file-system-state-store.js +2 -2
  70. package/lib/fs/file-system-state-store.js.map +1 -1
  71. package/lib/stripe/card.d.ts +171 -0
  72. package/lib/stripe/card.d.ts.map +1 -0
  73. package/lib/stripe/card.js +148 -0
  74. package/lib/stripe/card.js.map +1 -0
  75. package/lib/stripe/client.d.ts +9 -0
  76. package/lib/stripe/client.d.ts.map +1 -0
  77. package/lib/stripe/client.js +94 -0
  78. package/lib/stripe/client.js.map +1 -0
  79. package/lib/stripe/coupon.d.ts +135 -0
  80. package/lib/stripe/coupon.d.ts.map +1 -0
  81. package/lib/stripe/coupon.js +161 -0
  82. package/lib/stripe/coupon.js.map +1 -0
  83. package/lib/stripe/customer.d.ts +269 -0
  84. package/lib/stripe/customer.d.ts.map +1 -0
  85. package/lib/stripe/customer.js +289 -0
  86. package/lib/stripe/customer.js.map +1 -0
  87. package/lib/stripe/entitlements-feature.d.ts +84 -0
  88. package/lib/stripe/entitlements-feature.d.ts.map +1 -0
  89. package/lib/stripe/entitlements-feature.js +102 -0
  90. package/lib/stripe/entitlements-feature.js.map +1 -0
  91. package/lib/stripe/file.d.ts +143 -0
  92. package/lib/stripe/file.d.ts.map +1 -0
  93. package/lib/stripe/file.js +97 -0
  94. package/lib/stripe/file.js.map +1 -0
  95. package/lib/stripe/index.d.ts +11 -0
  96. package/lib/stripe/index.d.ts.map +1 -1
  97. package/lib/stripe/index.js +11 -0
  98. package/lib/stripe/index.js.map +1 -1
  99. package/lib/stripe/meter.d.ts +10 -1
  100. package/lib/stripe/meter.d.ts.map +1 -1
  101. package/lib/stripe/meter.js +13 -7
  102. package/lib/stripe/meter.js.map +1 -1
  103. package/lib/stripe/portal-configuration.d.ts +286 -0
  104. package/lib/stripe/portal-configuration.d.ts.map +1 -0
  105. package/lib/stripe/portal-configuration.js +320 -0
  106. package/lib/stripe/portal-configuration.js.map +1 -0
  107. package/lib/stripe/price.d.ts +10 -1
  108. package/lib/stripe/price.d.ts.map +1 -1
  109. package/lib/stripe/price.js +34 -13
  110. package/lib/stripe/price.js.map +1 -1
  111. package/lib/stripe/product-feature.d.ts +76 -0
  112. package/lib/stripe/product-feature.d.ts.map +1 -0
  113. package/lib/stripe/product-feature.js +74 -0
  114. package/lib/stripe/product-feature.js.map +1 -0
  115. package/lib/stripe/product.d.ts +10 -1
  116. package/lib/stripe/product.d.ts.map +1 -1
  117. package/lib/stripe/product.js +33 -14
  118. package/lib/stripe/product.js.map +1 -1
  119. package/lib/stripe/promotion-code.d.ts +138 -0
  120. package/lib/stripe/promotion-code.d.ts.map +1 -0
  121. package/lib/stripe/promotion-code.js +150 -0
  122. package/lib/stripe/promotion-code.js.map +1 -0
  123. package/lib/stripe/shipping-rate.d.ts +188 -0
  124. package/lib/stripe/shipping-rate.d.ts.map +1 -0
  125. package/lib/stripe/shipping-rate.js +198 -0
  126. package/lib/stripe/shipping-rate.js.map +1 -0
  127. package/lib/stripe/tax-rate.d.ts +130 -0
  128. package/lib/stripe/tax-rate.d.ts.map +1 -0
  129. package/lib/stripe/tax-rate.js +126 -0
  130. package/lib/stripe/tax-rate.js.map +1 -0
  131. package/lib/stripe/webhook.d.ts +10 -1
  132. package/lib/stripe/webhook.d.ts.map +1 -1
  133. package/lib/stripe/webhook.js +59 -14
  134. package/lib/stripe/webhook.js.map +1 -1
  135. package/lib/test/bun.d.ts.map +1 -1
  136. package/lib/test/bun.js +2 -6
  137. package/lib/test/bun.js.map +1 -1
  138. package/lib/test/vitest.d.ts.map +1 -1
  139. package/lib/test/vitest.js +3 -7
  140. package/lib/test/vitest.js.map +1 -1
  141. package/lib/util/retry.d.ts +1 -1
  142. package/lib/util/retry.d.ts.map +1 -1
  143. package/lib/util/retry.js.map +1 -1
  144. package/package.json +3 -2
  145. package/src/aws/control/client.ts +1 -1
  146. package/src/aws/control/resource.ts +2 -1
  147. package/src/aws/function.ts +3 -6
  148. package/src/cloudflare/ai.ts +21 -0
  149. package/src/cloudflare/bindings.ts +3 -2
  150. package/src/cloudflare/bound.ts +3 -3
  151. package/src/cloudflare/do-state-store/index.ts +1 -0
  152. package/src/cloudflare/do-state-store/internal.ts +215 -0
  153. package/src/cloudflare/do-state-store/store.ts +188 -0
  154. package/src/cloudflare/do-state-store/types.ts +74 -0
  155. package/src/cloudflare/do-state-store/worker.ts +199 -0
  156. package/src/cloudflare/index.ts +1 -0
  157. package/src/cloudflare/pipeline.ts +1 -1
  158. package/src/cloudflare/queue-consumer.ts +59 -1
  159. package/src/cloudflare/website.ts +38 -6
  160. package/src/cloudflare/worker-assets.ts +15 -3
  161. package/src/cloudflare/worker-metadata.ts +1 -1
  162. package/src/cloudflare/worker.ts +66 -46
  163. package/src/cloudflare/wrangler.json.ts +1 -1
  164. package/src/cloudflare/zone.ts +75 -2
  165. package/src/fs/file-system-state-store.ts +7 -2
  166. package/src/stripe/card.ts +320 -0
  167. package/src/stripe/client.ts +145 -0
  168. package/src/stripe/coupon.ts +280 -0
  169. package/src/stripe/customer.ts +528 -0
  170. package/src/stripe/entitlements-feature.ts +169 -0
  171. package/src/stripe/file.ts +219 -0
  172. package/src/stripe/index.ts +11 -0
  173. package/src/stripe/meter.ts +23 -7
  174. package/src/stripe/portal-configuration.ts +609 -0
  175. package/src/stripe/price.ts +47 -14
  176. package/src/stripe/product-feature.ts +150 -0
  177. package/src/stripe/product.ts +48 -15
  178. package/src/stripe/promotion-code.ts +268 -0
  179. package/src/stripe/shipping-rate.ts +362 -0
  180. package/src/stripe/tax-rate.ts +218 -0
  181. package/src/stripe/webhook.ts +82 -15
  182. package/src/test/bun.ts +2 -7
  183. package/src/test/vitest.ts +3 -8
  184. package/src/util/retry.ts +1 -1
@@ -0,0 +1,609 @@
1
+ import type Stripe from "stripe";
2
+ import type { Context } from "../context.ts";
3
+ import { Resource } from "../resource.ts";
4
+ import type { Secret } from "../secret.ts";
5
+ import {
6
+ createStripeClient,
7
+ handleStripeDeleteError,
8
+ isStripeConflictError,
9
+ } from "./client.ts";
10
+
11
+ /**
12
+ * Business profile information for the portal
13
+ */
14
+ export interface PortalConfigurationBusinessProfile {
15
+ /**
16
+ * The messaging shown to customers in the portal
17
+ */
18
+ headline?: string;
19
+ /**
20
+ * A link to the business's publicly available privacy policy
21
+ */
22
+ privacyPolicyUrl?: string;
23
+ /**
24
+ * A link to the business's publicly available terms of service
25
+ */
26
+ termsOfServiceUrl?: string;
27
+ }
28
+
29
+ /**
30
+ * Customer update feature configuration
31
+ */
32
+ export interface PortalCustomerUpdateFeature {
33
+ /**
34
+ * The types of customer update that are supported
35
+ */
36
+ allowedUpdates?: Array<Stripe.BillingPortal.ConfigurationCreateParams.Features.CustomerUpdate.AllowedUpdate>;
37
+ /**
38
+ * Whether the feature is enabled
39
+ */
40
+ enabled?: boolean;
41
+ }
42
+
43
+ /**
44
+ * Invoice history feature configuration
45
+ */
46
+ export interface PortalInvoiceHistoryFeature {
47
+ /**
48
+ * Whether the feature is enabled
49
+ */
50
+ enabled?: boolean;
51
+ }
52
+
53
+ /**
54
+ * Payment method update feature configuration
55
+ */
56
+ export interface PortalPaymentMethodUpdateFeature {
57
+ /**
58
+ * Whether the feature is enabled
59
+ */
60
+ enabled?: boolean;
61
+ }
62
+
63
+ /**
64
+ * Subscription cancellation reason configuration
65
+ */
66
+ export interface PortalSubscriptionCancellationReason {
67
+ /**
68
+ * Whether the feature is enabled
69
+ */
70
+ enabled?: boolean;
71
+ /**
72
+ * Which cancellation reasons will be given as options to the customer
73
+ */
74
+ options?: Array<Stripe.BillingPortal.ConfigurationCreateParams.Features.SubscriptionCancel.CancellationReason.Option>;
75
+ }
76
+
77
+ /**
78
+ * Subscription cancel feature configuration
79
+ */
80
+ export interface PortalSubscriptionCancelFeature {
81
+ /**
82
+ * Cancellation reason configuration
83
+ */
84
+ cancellationReason?: PortalSubscriptionCancellationReason;
85
+ /**
86
+ * Whether the feature is enabled
87
+ */
88
+ enabled?: boolean;
89
+ /**
90
+ * Whether to cancel subscriptions immediately or at the end of the billing period
91
+ */
92
+ mode?: Stripe.BillingPortal.ConfigurationCreateParams.Features.SubscriptionCancel.Mode;
93
+ /**
94
+ * Whether to create prorations when canceling subscriptions
95
+ */
96
+ prorationBehavior?: Stripe.BillingPortal.ConfigurationCreateParams.Features.SubscriptionCancel.ProrationBehavior;
97
+ }
98
+
99
+ /**
100
+ * Subscription pause feature configuration
101
+ */
102
+ export interface PortalSubscriptionPauseFeature {
103
+ /**
104
+ * Whether the feature is enabled
105
+ */
106
+ enabled?: boolean;
107
+ }
108
+
109
+ /**
110
+ * Subscription update product configuration
111
+ */
112
+ export interface PortalSubscriptionUpdateProduct {
113
+ product: string;
114
+ prices?: string[];
115
+ }
116
+
117
+ /**
118
+ * Subscription update feature configuration
119
+ */
120
+ export interface PortalSubscriptionUpdateFeature {
121
+ /**
122
+ * The types of subscription updates that are supported for items
123
+ */
124
+ defaultAllowedUpdates?: Array<Stripe.BillingPortal.ConfigurationCreateParams.Features.SubscriptionUpdate.DefaultAllowedUpdate>;
125
+ /**
126
+ * Whether the feature is enabled
127
+ */
128
+ enabled?: boolean;
129
+ /**
130
+ * The list of products that support subscription updates
131
+ */
132
+ products?: Array<PortalSubscriptionUpdateProduct>;
133
+ /**
134
+ * Determines how to handle prorations resulting from subscription updates
135
+ */
136
+ prorationBehavior?: Stripe.BillingPortal.ConfigurationCreateParams.Features.SubscriptionUpdate.ProrationBehavior;
137
+ }
138
+
139
+ /**
140
+ * Features available in the customer portal
141
+ */
142
+ export interface PortalConfigurationFeatures {
143
+ /**
144
+ * Information about updating the customer details in the portal
145
+ */
146
+ customerUpdate?: PortalCustomerUpdateFeature;
147
+ /**
148
+ * Information about showing the billing history in the portal
149
+ */
150
+ invoiceHistory?: PortalInvoiceHistoryFeature;
151
+ /**
152
+ * Information about updating payment methods in the portal
153
+ */
154
+ paymentMethodUpdate?: PortalPaymentMethodUpdateFeature;
155
+ /**
156
+ * Information about canceling subscriptions in the portal
157
+ */
158
+ subscriptionCancel?: PortalSubscriptionCancelFeature;
159
+ /**
160
+ * Information about pausing subscriptions in the portal
161
+ */
162
+ subscriptionPause?: PortalSubscriptionPauseFeature;
163
+ /**
164
+ * Information about updating subscriptions in the portal
165
+ */
166
+ subscriptionUpdate?: PortalSubscriptionUpdateFeature;
167
+ }
168
+
169
+ /**
170
+ * Properties for creating a Stripe portal configuration
171
+ */
172
+ export interface PortalConfigurationProps {
173
+ /**
174
+ * The business information shown to customers in the portal
175
+ */
176
+ businessProfile?: PortalConfigurationBusinessProfile;
177
+ /**
178
+ * The default URL to redirect customers to when they click on the portal's link to return to your website
179
+ */
180
+ defaultReturnUrl?: string;
181
+ /**
182
+ * Information about the features available in the portal
183
+ */
184
+ features?: PortalConfigurationFeatures;
185
+ /**
186
+ * Set of key-value pairs that you can attach to an object
187
+ */
188
+ metadata?: Record<string, string>;
189
+
190
+ /**
191
+ * API key to use (overrides environment variable)
192
+ */
193
+ apiKey?: Secret;
194
+
195
+ /**
196
+ * If true, adopt existing resource if creation fails due to conflict
197
+ */
198
+ adopt?: boolean;
199
+ }
200
+
201
+ /**
202
+ * Output from the Stripe portal configuration
203
+ */
204
+ export interface PortalConfiguration
205
+ extends Resource<"stripe::PortalConfiguration">,
206
+ PortalConfigurationProps {
207
+ /**
208
+ * The ID of the configuration
209
+ */
210
+ id: string;
211
+ /**
212
+ * String representing the object's type
213
+ */
214
+ object: "billing_portal.configuration";
215
+ /**
216
+ * Whether the configuration is active and can be used to create portal sessions
217
+ */
218
+ active: boolean;
219
+ /**
220
+ * ID of the Connect Application that created the configuration
221
+ */
222
+ application?: string;
223
+ /**
224
+ * Time at which the object was created
225
+ */
226
+ created: number;
227
+ /**
228
+ * Whether the configuration is the default
229
+ */
230
+ isDefault: boolean;
231
+ /**
232
+ * Has the value true if the object exists in live mode or the value false if the object exists in test mode
233
+ */
234
+ livemode: boolean;
235
+ /**
236
+ * Time at which the object was last updated
237
+ */
238
+ updated: number;
239
+ }
240
+
241
+ /**
242
+ * Create and manage Stripe customer portal configurations
243
+ *
244
+ * @example
245
+ * // Create a basic portal configuration
246
+ * const basicPortal = await PortalConfiguration("basic-portal", {
247
+ * businessProfile: {
248
+ * headline: "Manage your subscription",
249
+ * privacyPolicyUrl: "https://example.com/privacy",
250
+ * termsOfServiceUrl: "https://example.com/terms"
251
+ * },
252
+ * features: {
253
+ * invoiceHistory: { enabled: true },
254
+ * paymentMethodUpdate: { enabled: true }
255
+ * }
256
+ * });
257
+ *
258
+ * @example
259
+ * // Create a portal with subscription management
260
+ * const subscriptionPortal = await PortalConfiguration("subscription-portal", {
261
+ * defaultReturnUrl: "https://example.com/account",
262
+ * features: {
263
+ * subscriptionCancel: {
264
+ * enabled: true,
265
+ * mode: "at_period_end",
266
+ * prorationBehavior: "none"
267
+ * },
268
+ * subscriptionUpdate: {
269
+ * enabled: true,
270
+ * defaultAllowedUpdates: ["price", "quantity"],
271
+ * prorationBehavior: "create_prorations"
272
+ * }
273
+ * }
274
+ * });
275
+ *
276
+ * @example
277
+ * // Create a comprehensive portal configuration
278
+ * const fullPortal = await PortalConfiguration("full-portal", {
279
+ * businessProfile: {
280
+ * headline: "Manage your Acme Corp subscription",
281
+ * privacyPolicyUrl: "https://acme.com/privacy",
282
+ * termsOfServiceUrl: "https://acme.com/terms"
283
+ * },
284
+ * defaultReturnUrl: "https://acme.com/dashboard",
285
+ * features: {
286
+ * customerUpdate: {
287
+ * enabled: true,
288
+ * allowedUpdates: ["email", "address", "shipping", "phone", "tax_id"]
289
+ * },
290
+ * invoiceHistory: { enabled: true },
291
+ * paymentMethodUpdate: { enabled: true },
292
+ * subscriptionCancel: {
293
+ * enabled: true,
294
+ * mode: "immediately",
295
+ * cancellationReason: {
296
+ * enabled: true,
297
+ * options: ["too_expensive", "missing_features", "switched_service"]
298
+ * }
299
+ * }
300
+ * },
301
+ * metadata: {
302
+ * environment: "production",
303
+ * version: "v2"
304
+ * }
305
+ * });
306
+ */
307
+ export const PortalConfiguration = Resource(
308
+ "stripe::PortalConfiguration",
309
+ async function (
310
+ this: Context<PortalConfiguration>,
311
+ _id: string,
312
+ props: PortalConfigurationProps,
313
+ ): Promise<PortalConfiguration> {
314
+ const stripe = createStripeClient({ apiKey: props.apiKey });
315
+
316
+ if (this.phase === "delete") {
317
+ try {
318
+ if (this.output?.id) {
319
+ const existingConfig =
320
+ await stripe.billingPortal.configurations.retrieve(this.output.id);
321
+ if (!existingConfig.is_default) {
322
+ await stripe.billingPortal.configurations.update(this.output.id, {
323
+ active: false,
324
+ });
325
+ }
326
+ }
327
+ } catch (error) {
328
+ handleStripeDeleteError(error, "PortalConfiguration", this.output?.id);
329
+ }
330
+ return this.destroy();
331
+ }
332
+
333
+ try {
334
+ let configuration: Stripe.BillingPortal.Configuration;
335
+
336
+ if (this.phase === "update" && this.output?.id) {
337
+ const updateParams: Stripe.BillingPortal.ConfigurationUpdateParams = {
338
+ business_profile: props.businessProfile
339
+ ? {
340
+ headline: props.businessProfile.headline,
341
+ privacy_policy_url: props.businessProfile.privacyPolicyUrl,
342
+ terms_of_service_url: props.businessProfile.termsOfServiceUrl,
343
+ }
344
+ : undefined,
345
+ default_return_url: props.defaultReturnUrl,
346
+ metadata: props.metadata,
347
+ };
348
+
349
+ if (props.features) {
350
+ updateParams.features = {
351
+ customer_update: props.features.customerUpdate
352
+ ? {
353
+ allowed_updates:
354
+ props.features.customerUpdate.allowedUpdates || [],
355
+ enabled: props.features.customerUpdate.enabled || false,
356
+ }
357
+ : { enabled: false },
358
+ invoice_history: props.features.invoiceHistory
359
+ ? {
360
+ enabled: props.features.invoiceHistory.enabled || false,
361
+ }
362
+ : undefined,
363
+ payment_method_update: props.features.paymentMethodUpdate
364
+ ? {
365
+ enabled: props.features.paymentMethodUpdate.enabled || false,
366
+ }
367
+ : undefined,
368
+ subscription_cancel: props.features.subscriptionCancel
369
+ ? {
370
+ cancellation_reason: props.features.subscriptionCancel
371
+ .cancellationReason
372
+ ? {
373
+ enabled:
374
+ props.features.subscriptionCancel.cancellationReason
375
+ .enabled || false,
376
+ options:
377
+ props.features.subscriptionCancel.cancellationReason
378
+ .options || [],
379
+ }
380
+ : undefined,
381
+ enabled: props.features.subscriptionCancel.enabled || false,
382
+ mode: props.features.subscriptionCancel.mode || "immediately",
383
+ proration_behavior:
384
+ props.features.subscriptionCancel.prorationBehavior ||
385
+ "none",
386
+ }
387
+ : undefined,
388
+
389
+ subscription_update: props.features.subscriptionUpdate
390
+ ? {
391
+ default_allowed_updates:
392
+ props.features.subscriptionUpdate.defaultAllowedUpdates ||
393
+ [],
394
+ enabled: props.features.subscriptionUpdate.enabled || false,
395
+ products:
396
+ props.features.subscriptionUpdate.products?.map((p) => ({
397
+ product: p.product,
398
+ prices: p.prices || [],
399
+ })) || [],
400
+ proration_behavior:
401
+ props.features.subscriptionUpdate.prorationBehavior ||
402
+ "none",
403
+ }
404
+ : undefined,
405
+ };
406
+ }
407
+
408
+ configuration = await stripe.billingPortal.configurations.update(
409
+ this.output.id,
410
+ updateParams,
411
+ );
412
+ } else {
413
+ const createParams: Stripe.BillingPortal.ConfigurationCreateParams = {
414
+ business_profile: props.businessProfile
415
+ ? {
416
+ headline: props.businessProfile.headline,
417
+ privacy_policy_url: props.businessProfile.privacyPolicyUrl,
418
+ terms_of_service_url: props.businessProfile.termsOfServiceUrl,
419
+ }
420
+ : undefined,
421
+ default_return_url: props.defaultReturnUrl,
422
+ metadata: props.metadata,
423
+ features: {
424
+ customer_update: { enabled: false },
425
+ invoice_history: { enabled: true },
426
+ payment_method_update: { enabled: true },
427
+ subscription_cancel: { enabled: true },
428
+
429
+ subscription_update: {
430
+ enabled: false,
431
+ default_allowed_updates: [],
432
+ products: [],
433
+ proration_behavior: "none",
434
+ },
435
+ },
436
+ };
437
+
438
+ if (props.features) {
439
+ createParams.features = {
440
+ customer_update: props.features.customerUpdate
441
+ ? {
442
+ allowed_updates:
443
+ props.features.customerUpdate.allowedUpdates || [],
444
+ enabled: props.features.customerUpdate.enabled || false,
445
+ }
446
+ : { enabled: false },
447
+ invoice_history: props.features.invoiceHistory
448
+ ? {
449
+ enabled: props.features.invoiceHistory.enabled || false,
450
+ }
451
+ : undefined,
452
+ payment_method_update: props.features.paymentMethodUpdate
453
+ ? {
454
+ enabled: props.features.paymentMethodUpdate.enabled || false,
455
+ }
456
+ : undefined,
457
+ subscription_cancel: props.features.subscriptionCancel
458
+ ? {
459
+ cancellation_reason: props.features.subscriptionCancel
460
+ .cancellationReason
461
+ ? {
462
+ enabled:
463
+ props.features.subscriptionCancel.cancellationReason
464
+ .enabled || false,
465
+ options:
466
+ props.features.subscriptionCancel.cancellationReason
467
+ .options || [],
468
+ }
469
+ : undefined,
470
+ enabled: props.features.subscriptionCancel.enabled || false,
471
+ mode: props.features.subscriptionCancel.mode || "immediately",
472
+ proration_behavior:
473
+ props.features.subscriptionCancel.prorationBehavior ||
474
+ "none",
475
+ }
476
+ : undefined,
477
+
478
+ subscription_update: props.features.subscriptionUpdate
479
+ ? {
480
+ default_allowed_updates:
481
+ props.features.subscriptionUpdate.defaultAllowedUpdates ||
482
+ [],
483
+ enabled: props.features.subscriptionUpdate.enabled || false,
484
+ products:
485
+ props.features.subscriptionUpdate.products?.map((p) => ({
486
+ product: p.product,
487
+ prices: p.prices || [],
488
+ })) || [],
489
+ proration_behavior:
490
+ props.features.subscriptionUpdate.prorationBehavior ||
491
+ "none",
492
+ }
493
+ : undefined,
494
+ };
495
+ }
496
+
497
+ try {
498
+ configuration =
499
+ await stripe.billingPortal.configurations.create(createParams);
500
+ } catch (error) {
501
+ if (isStripeConflictError(error) && props.adopt) {
502
+ throw new Error(
503
+ "PortalConfiguration adoption is not supported - portal configurations cannot be uniquely identified for adoption",
504
+ );
505
+ } else {
506
+ throw error;
507
+ }
508
+ }
509
+ }
510
+
511
+ return this({
512
+ id: configuration.id,
513
+ object: configuration.object,
514
+ active: configuration.active,
515
+ application:
516
+ typeof configuration.application === "string"
517
+ ? configuration.application
518
+ : undefined,
519
+ businessProfile: configuration.business_profile
520
+ ? {
521
+ headline: configuration.business_profile.headline || undefined,
522
+ privacyPolicyUrl:
523
+ configuration.business_profile.privacy_policy_url || undefined,
524
+ termsOfServiceUrl:
525
+ configuration.business_profile.terms_of_service_url ||
526
+ undefined,
527
+ }
528
+ : undefined,
529
+ created: configuration.created,
530
+ defaultReturnUrl: configuration.default_return_url || undefined,
531
+ features: {
532
+ customerUpdate: configuration.features.customer_update
533
+ ? {
534
+ allowedUpdates:
535
+ configuration.features.customer_update.allowed_updates ||
536
+ undefined,
537
+ enabled:
538
+ configuration.features.customer_update.enabled || undefined,
539
+ }
540
+ : undefined,
541
+ invoiceHistory: configuration.features.invoice_history
542
+ ? {
543
+ enabled:
544
+ configuration.features.invoice_history.enabled || undefined,
545
+ }
546
+ : undefined,
547
+ paymentMethodUpdate: configuration.features.payment_method_update
548
+ ? {
549
+ enabled:
550
+ configuration.features.payment_method_update.enabled ||
551
+ undefined,
552
+ }
553
+ : undefined,
554
+ subscriptionCancel: configuration.features.subscription_cancel
555
+ ? {
556
+ cancellationReason: configuration.features.subscription_cancel
557
+ .cancellation_reason
558
+ ? {
559
+ enabled:
560
+ configuration.features.subscription_cancel
561
+ .cancellation_reason.enabled || undefined,
562
+ options:
563
+ configuration.features.subscription_cancel
564
+ .cancellation_reason.options || undefined,
565
+ }
566
+ : undefined,
567
+ enabled:
568
+ configuration.features.subscription_cancel.enabled ||
569
+ undefined,
570
+ mode:
571
+ configuration.features.subscription_cancel.mode || undefined,
572
+ prorationBehavior:
573
+ configuration.features.subscription_cancel
574
+ .proration_behavior || undefined,
575
+ }
576
+ : undefined,
577
+
578
+ subscriptionUpdate: configuration.features.subscription_update
579
+ ? {
580
+ defaultAllowedUpdates:
581
+ configuration.features.subscription_update
582
+ .default_allowed_updates || undefined,
583
+ enabled:
584
+ configuration.features.subscription_update.enabled ||
585
+ undefined,
586
+ products:
587
+ configuration.features.subscription_update.products?.map(
588
+ (p) => ({
589
+ product: p.product,
590
+ prices: p.prices || undefined,
591
+ }),
592
+ ) || undefined,
593
+ prorationBehavior:
594
+ configuration.features.subscription_update
595
+ .proration_behavior || undefined,
596
+ }
597
+ : undefined,
598
+ },
599
+ isDefault: configuration.is_default,
600
+ livemode: configuration.livemode,
601
+ metadata: configuration.metadata || undefined,
602
+ updated: configuration.updated,
603
+ });
604
+ } catch (error) {
605
+ console.error("Error creating/updating portal configuration:", error);
606
+ throw error;
607
+ }
608
+ },
609
+ );
@@ -1,6 +1,8 @@
1
- import Stripe from "stripe";
1
+ import type Stripe from "stripe";
2
2
  import type { Context } from "../context.ts";
3
3
  import { Resource } from "../resource.ts";
4
+ import type { Secret } from "../secret.ts";
5
+ import { createStripeClient, handleStripeDeleteError } from "./client.ts";
4
6
 
5
7
  /**
6
8
  * Properties for price recurring configuration
@@ -106,6 +108,16 @@ export interface PriceProps {
106
108
  * If set to true, will atomically transfer the lookup key from an existing price to this price.
107
109
  */
108
110
  transferLookupKey?: boolean;
111
+
112
+ /**
113
+ * API key to use (overrides environment variable)
114
+ */
115
+ apiKey?: Secret;
116
+
117
+ /**
118
+ * If true, adopt existing resource if creation fails due to conflict
119
+ */
120
+ adopt?: boolean;
109
121
  }
110
122
 
111
123
  /**
@@ -195,14 +207,7 @@ export const Price = Resource(
195
207
  _id: string,
196
208
  props: PriceProps,
197
209
  ): Promise<Price> {
198
- // Get Stripe API key from context or environment
199
- const apiKey = process.env.STRIPE_API_KEY;
200
- if (!apiKey) {
201
- throw new Error("STRIPE_API_KEY environment variable is required");
202
- }
203
-
204
- // Initialize Stripe client
205
- const stripe = new Stripe(apiKey);
210
+ const stripe = createStripeClient({ apiKey: props.apiKey });
206
211
 
207
212
  if (this.phase === "delete") {
208
213
  try {
@@ -211,8 +216,7 @@ export const Price = Resource(
211
216
  await stripe.prices.update(this.output.id, { active: false });
212
217
  }
213
218
  } catch (error) {
214
- // Ignore if the price doesn't exist
215
- console.error("Error deactivating price:", error);
219
+ handleStripeDeleteError(error, "Price", this.output?.id);
216
220
  }
217
221
 
218
222
  return this.destroy();
@@ -222,13 +226,15 @@ export const Price = Resource(
222
226
 
223
227
  if (this.phase === "update" && this.output?.id) {
224
228
  // Update existing price (limited properties can be updated)
225
- price = await stripe.prices.update(this.output.id, {
229
+ const updateParams: Stripe.PriceUpdateParams = {
226
230
  active: props.active,
227
231
  metadata: props.metadata,
228
232
  nickname: props.nickname,
229
233
  lookup_key: props.lookupKey,
230
234
  transfer_lookup_key: props.transferLookupKey,
231
- });
235
+ };
236
+
237
+ price = await stripe.prices.update(this.output.id, updateParams);
232
238
  } else {
233
239
  // Create new price
234
240
  const createParams: Stripe.PriceCreateParams = {
@@ -260,7 +266,34 @@ export const Price = Resource(
260
266
  };
261
267
  }
262
268
 
263
- price = await stripe.prices.create(createParams);
269
+ if (props.lookupKey) {
270
+ const existingPrices = await stripe.prices.list({
271
+ lookup_keys: [props.lookupKey],
272
+ limit: 1,
273
+ });
274
+ if (existingPrices.data.length > 0) {
275
+ if (props.adopt) {
276
+ const existingPrice = existingPrices.data[0];
277
+ const updateParams: Stripe.PriceUpdateParams = {
278
+ active: props.active,
279
+ metadata: props.metadata,
280
+ nickname: props.nickname,
281
+ lookup_key: props.lookupKey,
282
+ transfer_lookup_key: props.transferLookupKey,
283
+ };
284
+ price = await stripe.prices.update(
285
+ existingPrice.id,
286
+ updateParams,
287
+ );
288
+ } else {
289
+ price = existingPrices.data[0];
290
+ }
291
+ } else {
292
+ price = await stripe.prices.create(createParams);
293
+ }
294
+ } else {
295
+ price = await stripe.prices.create(createParams);
296
+ }
264
297
  }
265
298
 
266
299
  // Transform Stripe recurring object to our format