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,150 @@
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
+ * Properties for creating a Stripe product feature
13
+ */
14
+ export interface ProductFeatureProps {
15
+ /**
16
+ * The ID of the product that this feature will be attached to
17
+ */
18
+ product: string;
19
+
20
+ /**
21
+ * The ID of the entitlements feature to attach to the product
22
+ */
23
+ entitlementFeature: string;
24
+
25
+ /**
26
+ * API key to use (overrides environment variable)
27
+ */
28
+ apiKey?: Secret;
29
+
30
+ /**
31
+ * If true, adopt existing resource if creation fails due to conflict
32
+ */
33
+ adopt?: boolean;
34
+ }
35
+
36
+ /**
37
+ * Output from the Stripe product feature
38
+ */
39
+ export interface ProductFeature
40
+ extends Resource<"stripe::ProductFeature">,
41
+ ProductFeatureProps {
42
+ /**
43
+ * The ID of the product feature
44
+ */
45
+ id: string;
46
+
47
+ /**
48
+ * String representing the object's type
49
+ */
50
+ object: "product_feature";
51
+
52
+ /**
53
+ * The entitlement feature object attached to this product
54
+ */
55
+ entitlementFeatureObject?: {
56
+ id: string;
57
+ name: string;
58
+ lookupKey?: string;
59
+ metadata?: Record<string, string>;
60
+ };
61
+
62
+ /**
63
+ * Has the value true if the object exists in live mode or the value false if the object exists in test mode
64
+ */
65
+ livemode: boolean;
66
+ }
67
+
68
+ /**
69
+ * Create and manage Stripe product features
70
+ *
71
+ * @example
72
+ * // Attach an API access feature to a product
73
+ * const productApiFeature = await ProductFeature("product-api-access", {
74
+ * product: "prod_xyz123",
75
+ * entitlementFeature: "feat_abc456"
76
+ * });
77
+ *
78
+ * @example
79
+ * // Attach analytics feature to a premium product
80
+ * const productAnalytics = await ProductFeature("premium-analytics", {
81
+ * product: "prod_premium789",
82
+ * entitlementFeature: "feat_analytics123"
83
+ * });
84
+ *
85
+ * @example
86
+ * // Attach custom integrations to enterprise product
87
+ * const enterpriseIntegrations = await ProductFeature("enterprise-integrations", {
88
+ * product: "prod_enterprise456",
89
+ * entitlementFeature: "feat_integrations789"
90
+ * });
91
+ */
92
+ export const ProductFeature = Resource(
93
+ "stripe::ProductFeature",
94
+ async function (
95
+ this: Context<ProductFeature>,
96
+ _id: string,
97
+ props: ProductFeatureProps,
98
+ ): Promise<ProductFeature> {
99
+ const stripe = createStripeClient({ apiKey: props.apiKey });
100
+
101
+ if (this.phase === "delete") {
102
+ try {
103
+ if (this.output?.id && this.output?.product) {
104
+ await stripe.products.deleteFeature(
105
+ this.output.product,
106
+ this.output.id,
107
+ );
108
+ }
109
+ } catch (error) {
110
+ handleStripeDeleteError(error, "ProductFeature", this.output?.id);
111
+ }
112
+ return this.destroy();
113
+ }
114
+
115
+ try {
116
+ let productFeature: Stripe.ProductFeature;
117
+
118
+ if (this.phase === "update" && this.output?.id) {
119
+ throw new Error("Product features cannot be updated after creation");
120
+ } else {
121
+ try {
122
+ productFeature = await stripe.products.createFeature(props.product, {
123
+ entitlement_feature: props.entitlementFeature,
124
+ });
125
+ } catch (error) {
126
+ if (isStripeConflictError(error) && props.adopt) {
127
+ throw new Error(
128
+ "ProductFeature adoption is not supported - product features cannot be uniquely identified for adoption",
129
+ );
130
+ } else {
131
+ throw error;
132
+ }
133
+ }
134
+ }
135
+
136
+ return this({
137
+ id: productFeature.id,
138
+ object: productFeature.object,
139
+ product: props.product,
140
+ entitlementFeature: props.entitlementFeature,
141
+ entitlementFeatureObject:
142
+ productFeature.entitlement_feature || undefined,
143
+ livemode: productFeature.livemode,
144
+ });
145
+ } catch (error) {
146
+ console.error("Error creating product feature:", error);
147
+ throw error;
148
+ }
149
+ },
150
+ );
@@ -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
  type ProductType = Stripe.Product.Type;
6
8
 
@@ -62,6 +64,16 @@ export interface ProductProps {
62
64
  * Default tax code for the product
63
65
  */
64
66
  taxCode?: string;
67
+
68
+ /**
69
+ * API key to use (overrides environment variable)
70
+ */
71
+ apiKey?: Secret;
72
+
73
+ /**
74
+ * If true, adopt existing resource if creation fails due to conflict
75
+ */
76
+ adopt?: boolean;
65
77
  }
66
78
 
67
79
  /**
@@ -144,14 +156,7 @@ export const Product = Resource(
144
156
  _id: string,
145
157
  props: ProductProps,
146
158
  ): Promise<Product> {
147
- // Get Stripe API key from context or environment
148
- const apiKey = process.env.STRIPE_API_KEY;
149
- if (!apiKey) {
150
- throw new Error("STRIPE_API_KEY environment variable is required");
151
- }
152
-
153
- // Initialize Stripe client
154
- const stripe = new Stripe(apiKey);
159
+ const stripe = createStripeClient({ apiKey: props.apiKey });
155
160
 
156
161
  if (this.phase === "delete") {
157
162
  try {
@@ -159,8 +164,7 @@ export const Product = Resource(
159
164
  await stripe.products.update(this.output.id, { active: false });
160
165
  }
161
166
  } catch (error) {
162
- // Ignore if the product doesn't exist
163
- console.error("Error deactivating product:", error);
167
+ handleStripeDeleteError(error, "Product", this.output?.id);
164
168
  }
165
169
 
166
170
  // Return a minimal output for deleted state
@@ -171,7 +175,7 @@ export const Product = Resource(
171
175
 
172
176
  if (this.phase === "update" && this.output?.id) {
173
177
  // Update existing product
174
- product = await stripe.products.update(this.output.id, {
178
+ const updateParams = {
175
179
  name: props.name,
176
180
  description: props.description,
177
181
  active: props.active,
@@ -180,10 +184,11 @@ export const Product = Resource(
180
184
  statement_descriptor: props.statementDescriptor,
181
185
  metadata: props.metadata,
182
186
  tax_code: props.taxCode,
183
- });
187
+ };
188
+ product = await stripe.products.update(this.output.id, updateParams);
184
189
  } else {
185
190
  // Create new product
186
- product = await stripe.products.create({
191
+ const createParams = {
187
192
  name: props.name,
188
193
  description: props.description,
189
194
  active: props.active,
@@ -195,7 +200,35 @@ export const Product = Resource(
195
200
  statement_descriptor: props.statementDescriptor,
196
201
  metadata: props.metadata,
197
202
  tax_code: props.taxCode,
198
- });
203
+ };
204
+ if (props.adopt) {
205
+ const existingProducts = await stripe.products.list({
206
+ limit: 100,
207
+ });
208
+ const existingProduct = existingProducts.data.find(
209
+ (p) => p.name === props.name,
210
+ );
211
+ if (existingProduct) {
212
+ const updateParams = {
213
+ name: props.name,
214
+ description: props.description,
215
+ active: props.active,
216
+ images: props.images,
217
+ url: props.url,
218
+ statement_descriptor: props.statementDescriptor,
219
+ metadata: props.metadata,
220
+ tax_code: props.taxCode,
221
+ };
222
+ product = await stripe.products.update(
223
+ existingProduct.id,
224
+ updateParams,
225
+ );
226
+ } else {
227
+ product = await stripe.products.create(createParams);
228
+ }
229
+ } else {
230
+ product = await stripe.products.create(createParams);
231
+ }
199
232
  }
200
233
 
201
234
  return this({
@@ -0,0 +1,268 @@
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
+ * Restrictions for promotion code usage
13
+ */
14
+ export interface PromotionCodeRestrictions {
15
+ /**
16
+ * Promotion codes defined with this restriction will only work for Checkout Sessions in payment mode
17
+ */
18
+ firstTimeTransaction?: boolean;
19
+ /**
20
+ * Minimum amount required to redeem this promotion code into a coupon
21
+ */
22
+ minimumAmount?: number;
23
+ /**
24
+ * Three-letter ISO code for minimum_amount
25
+ */
26
+ minimumAmountCurrency?: string;
27
+ }
28
+
29
+ /**
30
+ * Properties for creating a Stripe promotion code
31
+ */
32
+ export interface PromotionCodeProps {
33
+ /**
34
+ * The coupon for this promotion code
35
+ */
36
+ coupon: string;
37
+ /**
38
+ * The customer-facing code. Regardless of case, this code must be unique across all active promotion codes for a specific customer
39
+ */
40
+ code?: string;
41
+ /**
42
+ * Whether the promotion code is currently active
43
+ */
44
+ active?: boolean;
45
+ /**
46
+ * The customer that this promotion code can be used by
47
+ */
48
+ customer?: string;
49
+ /**
50
+ * The timestamp at which this promotion code will expire
51
+ */
52
+ expiresAt?: number;
53
+ /**
54
+ * The maximum number of times this promotion code can be redeemed
55
+ */
56
+ maxRedemptions?: number;
57
+ /**
58
+ * Set of key-value pairs that you can attach to an object
59
+ */
60
+ metadata?: Record<string, string>;
61
+ /**
62
+ * Settings that restrict the redemption of the promotion code
63
+ */
64
+ restrictions?: PromotionCodeRestrictions;
65
+
66
+ /**
67
+ * API key to use (overrides environment variable)
68
+ */
69
+ apiKey?: Secret;
70
+
71
+ /**
72
+ * If true, adopt existing resource if creation fails due to conflict
73
+ */
74
+ adopt?: boolean;
75
+ }
76
+
77
+ /**
78
+ * Output from the Stripe promotion code
79
+ */
80
+ export interface PromotionCode
81
+ extends Resource<"stripe::PromotionCode">,
82
+ PromotionCodeProps {
83
+ /**
84
+ * The ID of the promotion code
85
+ */
86
+ id: string;
87
+ /**
88
+ * String representing the object's type
89
+ */
90
+ object: "promotion_code";
91
+ /**
92
+ * Time at which the object was created
93
+ */
94
+ created: number;
95
+ /**
96
+ * Has the value true if the object exists in live mode or the value false if the object exists in test mode
97
+ */
98
+ livemode: boolean;
99
+ /**
100
+ * Number of times this promotion code has been used
101
+ */
102
+ timesRedeemed: number;
103
+ }
104
+
105
+ /**
106
+ * Create and manage Stripe promotion codes for coupons
107
+ *
108
+ * @example
109
+ * // Create a basic promotion code
110
+ * const basicPromoCode = await PromotionCode("summer-promo", {
111
+ * coupon: "SUMMER25",
112
+ * code: "SAVE25NOW",
113
+ * active: true,
114
+ * metadata: {
115
+ * campaign: "summer_sale",
116
+ * channel: "email"
117
+ * }
118
+ * });
119
+ *
120
+ * @example
121
+ * // Create a customer-specific promotion code
122
+ * const customerPromoCode = await PromotionCode("vip-discount", {
123
+ * coupon: "VIP15",
124
+ * code: "VIP15OFF",
125
+ * customer: "cus_xyz123",
126
+ * maxRedemptions: 1,
127
+ * expiresAt: Math.floor(Date.now() / 1000) + 86400 * 30,
128
+ * metadata: {
129
+ * type: "vip_exclusive",
130
+ * tier: "gold"
131
+ * }
132
+ * });
133
+ *
134
+ * @example
135
+ * // Create a promotion code with restrictions
136
+ * const restrictedPromoCode = await PromotionCode("first-time-buyer", {
137
+ * coupon: "WELCOME10",
138
+ * code: "FIRSTTIME10",
139
+ * restrictions: {
140
+ * firstTimeTransaction: true,
141
+ * minimumAmount: 5000,
142
+ * minimumAmountCurrency: "usd"
143
+ * },
144
+ * maxRedemptions: 1000,
145
+ * metadata: {
146
+ * campaign: "new_customer_acquisition",
147
+ * minimum_order: "50_usd"
148
+ * }
149
+ * });
150
+ */
151
+ export const PromotionCode = Resource(
152
+ "stripe::PromotionCode",
153
+ async function (
154
+ this: Context<PromotionCode>,
155
+ _id: string,
156
+ props: PromotionCodeProps,
157
+ ): Promise<PromotionCode> {
158
+ const stripe = createStripeClient({ apiKey: props.apiKey });
159
+
160
+ if (this.phase === "delete") {
161
+ try {
162
+ if (this.output?.id) {
163
+ await stripe.promotionCodes.update(this.output.id, { active: false });
164
+ }
165
+ } catch (error) {
166
+ handleStripeDeleteError(error, "PromotionCode", this.output?.id);
167
+ }
168
+ return this.destroy();
169
+ }
170
+
171
+ try {
172
+ let promotionCode: Stripe.PromotionCode;
173
+
174
+ if (this.phase === "update" && this.output?.id) {
175
+ const updateParams: Stripe.PromotionCodeUpdateParams = {
176
+ active: props.active,
177
+ metadata: props.metadata,
178
+ };
179
+ promotionCode = await stripe.promotionCodes.update(
180
+ this.output.id,
181
+ updateParams,
182
+ );
183
+ } else {
184
+ const createParams: Stripe.PromotionCodeCreateParams = {
185
+ coupon: props.coupon,
186
+ code: props.code,
187
+ active: props.active,
188
+ customer: props.customer,
189
+ expires_at: props.expiresAt,
190
+ max_redemptions: props.maxRedemptions,
191
+ metadata: props.metadata,
192
+ };
193
+
194
+ if (props.restrictions) {
195
+ createParams.restrictions = {
196
+ first_time_transaction: props.restrictions.firstTimeTransaction,
197
+ minimum_amount: props.restrictions.minimumAmount,
198
+ minimum_amount_currency: props.restrictions.minimumAmountCurrency,
199
+ };
200
+ }
201
+
202
+ try {
203
+ promotionCode = await stripe.promotionCodes.create(createParams);
204
+ } catch (error) {
205
+ if (isStripeConflictError(error) && props.adopt) {
206
+ if (props.code) {
207
+ const existingPromotionCodes = await stripe.promotionCodes.list({
208
+ code: props.code,
209
+ limit: 1,
210
+ });
211
+ if (existingPromotionCodes.data.length > 0) {
212
+ const existingPromotionCode = existingPromotionCodes.data[0];
213
+ const updateParams: Stripe.PromotionCodeUpdateParams = {
214
+ active: props.active,
215
+ metadata: props.metadata,
216
+ };
217
+ promotionCode = await stripe.promotionCodes.update(
218
+ existingPromotionCode.id,
219
+ updateParams,
220
+ );
221
+ } else {
222
+ throw error;
223
+ }
224
+ } else {
225
+ throw error;
226
+ }
227
+ } else {
228
+ throw error;
229
+ }
230
+ }
231
+ }
232
+
233
+ return this({
234
+ id: promotionCode.id,
235
+ object: promotionCode.object,
236
+ coupon:
237
+ typeof promotionCode.coupon === "string"
238
+ ? promotionCode.coupon
239
+ : promotionCode.coupon.id,
240
+ code: promotionCode.code,
241
+ active: promotionCode.active,
242
+ customer:
243
+ typeof promotionCode.customer === "string"
244
+ ? promotionCode.customer
245
+ : promotionCode.customer?.id,
246
+ expiresAt: promotionCode.expires_at || undefined,
247
+ maxRedemptions: promotionCode.max_redemptions || undefined,
248
+ metadata: promotionCode.metadata || undefined,
249
+ restrictions: promotionCode.restrictions
250
+ ? {
251
+ firstTimeTransaction:
252
+ promotionCode.restrictions.first_time_transaction || undefined,
253
+ minimumAmount:
254
+ promotionCode.restrictions.minimum_amount || undefined,
255
+ minimumAmountCurrency:
256
+ promotionCode.restrictions.minimum_amount_currency || undefined,
257
+ }
258
+ : undefined,
259
+ created: promotionCode.created,
260
+ livemode: promotionCode.livemode,
261
+ timesRedeemed: promotionCode.times_redeemed,
262
+ });
263
+ } catch (error) {
264
+ console.error("Error creating/updating promotion code:", error);
265
+ throw error;
266
+ }
267
+ },
268
+ );