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
@@ -165,9 +165,9 @@ export interface ZoneProps extends CloudflareApiOptions {
165
165
  }
166
166
 
167
167
  /**
168
- * Output returned after Zone creation/update
168
+ * Zone data structure (used for lookup functions)
169
169
  */
170
- export interface Zone extends Resource<"cloudflare::Zone"> {
170
+ export interface ZoneData {
171
171
  /**
172
172
  * The ID of the zone
173
173
  */
@@ -246,6 +246,11 @@ export interface Zone extends Resource<"cloudflare::Zone"> {
246
246
  };
247
247
  }
248
248
 
249
+ /**
250
+ * Output returned after Zone creation/update
251
+ */
252
+ export interface Zone extends Resource<"cloudflare::Zone">, ZoneData {}
253
+
249
254
  /**
250
255
  * A Cloudflare Zone represents a domain and its configuration settings on Cloudflare.
251
256
  * Zones allow you to manage DNS, SSL/TLS, caching, security and other settings for a domain.
@@ -539,6 +544,74 @@ async function getZoneSettings(
539
544
  };
540
545
  }
541
546
 
547
+ /**
548
+ * Look up a Cloudflare zone by domain name
549
+ *
550
+ * @param domainName The domain name to look up (e.g., "example.com")
551
+ * @param options Optional API configuration
552
+ * @returns Promise resolving to zone details or null if not found
553
+ *
554
+ * @example
555
+ * // Look up a zone by domain name
556
+ * const zone = await getZoneByDomain("example.com");
557
+ * if (zone) {
558
+ * console.log(`Zone ID: ${zone.id}`);
559
+ * console.log(`Nameservers: ${zone.nameservers.join(", ")}`);
560
+ * }
561
+ *
562
+ * @example
563
+ * // Look up a zone with custom API options
564
+ * const zone = await getZoneByDomain("example.com", {
565
+ * apiToken: myApiToken,
566
+ * accountId: "my-account-id"
567
+ * });
568
+ */
569
+ export async function getZoneByDomain(
570
+ domainName: string,
571
+ options: Partial<CloudflareApiOptions> = {},
572
+ ): Promise<ZoneData | null> {
573
+ const api = await createCloudflareApi(options);
574
+
575
+ const response = await api.get(
576
+ `/zones?name=${encodeURIComponent(domainName)}`,
577
+ );
578
+
579
+ if (!response.ok) {
580
+ throw new Error(
581
+ `Error fetching zone for '${domainName}': ${response.statusText}`,
582
+ );
583
+ }
584
+
585
+ const zones = ((await response.json()) as { result: CloudflareZone[] })
586
+ .result;
587
+
588
+ if (zones.length === 0) {
589
+ return null;
590
+ }
591
+
592
+ const zoneData = zones[0];
593
+
594
+ // Get zone settings
595
+ const settings = await getZoneSettings(api, zoneData.id);
596
+
597
+ return {
598
+ id: zoneData.id,
599
+ name: zoneData.name,
600
+ type: zoneData.type,
601
+ status: zoneData.status,
602
+ paused: zoneData.paused,
603
+ accountId: zoneData.account.id,
604
+ nameservers: zoneData.name_servers,
605
+ originalNameservers: zoneData.original_name_servers,
606
+ createdAt: new Date(zoneData.created_on).getTime(),
607
+ modifiedAt: new Date(zoneData.modified_on).getTime(),
608
+ activatedAt: zoneData.activated_on
609
+ ? new Date(zoneData.activated_on).getTime()
610
+ : null,
611
+ settings,
612
+ };
613
+ }
614
+
542
615
  /**
543
616
  * Cloudflare Zone response format
544
617
  */
@@ -11,8 +11,13 @@ const stateRootDir = path.join(process.cwd(), ".alchemy");
11
11
  export class FileSystemStateStore implements StateStore {
12
12
  public readonly dir: string;
13
13
  private initialized = false;
14
- constructor(public readonly scope: Scope) {
15
- this.dir = path.join(stateRootDir, ...scope.chain);
14
+ constructor(
15
+ public readonly scope: Scope,
16
+ options?: {
17
+ rootDir?: string;
18
+ },
19
+ ) {
20
+ this.dir = path.join(options?.rootDir ?? stateRootDir, ...scope.chain);
16
21
  }
17
22
 
18
23
  async init(): Promise<void> {
@@ -0,0 +1,320 @@
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 card
13
+ */
14
+ export interface CardProps {
15
+ /**
16
+ * The ID of the customer to attach the card to
17
+ */
18
+ customer: string;
19
+
20
+ /**
21
+ * A token or source to attach to the customer
22
+ */
23
+ source?: string;
24
+
25
+ /**
26
+ * The card number, as a string without any separators
27
+ */
28
+ number?: string;
29
+
30
+ /**
31
+ * Two-digit number representing the card's expiration month
32
+ */
33
+ expMonth?: number;
34
+
35
+ /**
36
+ * Four-digit number representing the card's expiration year
37
+ */
38
+ expYear?: number;
39
+
40
+ /**
41
+ * Card security code
42
+ */
43
+ cvc?: string;
44
+
45
+ /**
46
+ * Cardholder's full name
47
+ */
48
+ name?: string;
49
+
50
+ /**
51
+ * City/District/Suburb/Town/Village
52
+ */
53
+ addressCity?: string;
54
+
55
+ /**
56
+ * Billing address country, if provided when creating card
57
+ */
58
+ addressCountry?: string;
59
+
60
+ /**
61
+ * Address line 1 (Street address/PO Box/Company name)
62
+ */
63
+ addressLine1?: string;
64
+
65
+ /**
66
+ * Address line 2 (Apartment/Suite/Unit/Building)
67
+ */
68
+ addressLine2?: string;
69
+
70
+ /**
71
+ * State/County/Province/Region
72
+ */
73
+ addressState?: string;
74
+
75
+ /**
76
+ * ZIP or postal code
77
+ */
78
+ addressZip?: string;
79
+
80
+ /**
81
+ * Required when adding a card to an account (not applicable to customers or recipients)
82
+ */
83
+ currency?: string;
84
+
85
+ /**
86
+ * Only applicable on accounts (not customers or recipients)
87
+ */
88
+ defaultForCurrency?: boolean;
89
+
90
+ /**
91
+ * Set of key-value pairs that you can attach to an object
92
+ */
93
+ metadata?: Record<string, string>;
94
+
95
+ /**
96
+ * API key to use (overrides environment variable)
97
+ */
98
+ apiKey?: Secret;
99
+
100
+ /**
101
+ * If true, adopt existing resource if creation fails due to conflict
102
+ */
103
+ adopt?: boolean;
104
+ }
105
+
106
+ /**
107
+ * Output from the Stripe card
108
+ */
109
+ export interface Card extends Resource<"stripe::Card">, CardProps {
110
+ /**
111
+ * The ID of the card
112
+ */
113
+ id: string;
114
+
115
+ /**
116
+ * String representing the object's type
117
+ */
118
+ object: "card";
119
+
120
+ /**
121
+ * Card brand (Visa, American Express, MasterCard, Discover, JCB, Diners Club, or Unknown)
122
+ */
123
+ brand: string;
124
+
125
+ /**
126
+ * Two-letter ISO code representing the country of the card
127
+ */
128
+ country?: string;
129
+
130
+ /**
131
+ * The last four digits of the device account number
132
+ */
133
+ dynamicLast4?: string;
134
+
135
+ /**
136
+ * Uniquely identifies this particular card number
137
+ */
138
+ fingerprint?: string;
139
+
140
+ /**
141
+ * Card funding type (credit, debit, prepaid, or unknown)
142
+ */
143
+ funding: string;
144
+
145
+ /**
146
+ * The last four digits of the card
147
+ */
148
+ last4: string;
149
+
150
+ /**
151
+ * If the card number is tokenized, this is the method that was used
152
+ */
153
+ tokenizationMethod?: string;
154
+ }
155
+
156
+ /**
157
+ * Create and manage Stripe cards attached to customers
158
+ *
159
+ * @example
160
+ * // Create a card using raw card details
161
+ * const customerCard = await Card("customer-card", {
162
+ * customer: "cus_xyz123",
163
+ * number: "4242424242424242",
164
+ * expMonth: 12,
165
+ * expYear: 2025,
166
+ * cvc: "123",
167
+ * name: "John Doe",
168
+ * addressLine1: "123 Main St",
169
+ * addressCity: "San Francisco",
170
+ * addressState: "CA",
171
+ * addressZip: "94105",
172
+ * addressCountry: "US"
173
+ * });
174
+ *
175
+ * @example
176
+ * // Create a card using a token
177
+ * const tokenCard = await Card("token-card", {
178
+ * customer: "cus_xyz123",
179
+ * source: "tok_visa",
180
+ * metadata: {
181
+ * type: "primary",
182
+ * source: "mobile_app"
183
+ * }
184
+ * });
185
+ *
186
+ * @example
187
+ * // Create a card for international customer
188
+ * const internationalCard = await Card("international-card", {
189
+ * customer: "cus_international456",
190
+ * number: "4000000000000002",
191
+ * expMonth: 6,
192
+ * expYear: 2026,
193
+ * name: "Maria Garcia",
194
+ * addressLine1: "Calle Principal 123",
195
+ * addressCity: "Madrid",
196
+ * addressCountry: "ES",
197
+ * addressZip: "28001",
198
+ * metadata: {
199
+ * region: "europe",
200
+ * currency_preference: "eur"
201
+ * }
202
+ * });
203
+ */
204
+ export const Card = Resource(
205
+ "stripe::Card",
206
+ async function (
207
+ this: Context<Card>,
208
+ _id: string,
209
+ props: CardProps,
210
+ ): Promise<Card> {
211
+ const stripe = createStripeClient({ apiKey: props.apiKey });
212
+
213
+ if (this.phase === "delete") {
214
+ try {
215
+ if (this.output?.id && this.output?.customer) {
216
+ await stripe.customers.deleteSource(
217
+ this.output.customer,
218
+ this.output.id,
219
+ );
220
+ }
221
+ } catch (error) {
222
+ handleStripeDeleteError(error, "Card", this.output?.id);
223
+ }
224
+ return this.destroy();
225
+ }
226
+
227
+ try {
228
+ let card: Stripe.Card;
229
+
230
+ if (this.phase === "update" && this.output?.id) {
231
+ const updateParams = {
232
+ name: props.name,
233
+ address_city: props.addressCity,
234
+ address_country: props.addressCountry,
235
+ address_line1: props.addressLine1,
236
+ address_line2: props.addressLine2,
237
+ address_state: props.addressState,
238
+ address_zip: props.addressZip,
239
+ exp_month: props.expMonth?.toString(),
240
+ exp_year: props.expYear?.toString(),
241
+ metadata: props.metadata,
242
+ };
243
+
244
+ card = (await stripe.customers.updateSource(
245
+ props.customer,
246
+ this.output.id,
247
+ updateParams,
248
+ )) as Stripe.Card;
249
+ } else {
250
+ const createParams: any = {
251
+ metadata: props.metadata,
252
+ };
253
+
254
+ if (props.source) {
255
+ createParams.source = props.source;
256
+ } else if (props.number) {
257
+ createParams.source = {
258
+ object: "card",
259
+ number: props.number,
260
+ exp_month: props.expMonth,
261
+ exp_year: props.expYear,
262
+ cvc: props.cvc,
263
+ name: props.name,
264
+ address_city: props.addressCity,
265
+ address_country: props.addressCountry,
266
+ address_line1: props.addressLine1,
267
+ address_line2: props.addressLine2,
268
+ address_state: props.addressState,
269
+ address_zip: props.addressZip,
270
+ currency: props.currency,
271
+ default_for_currency: props.defaultForCurrency,
272
+ };
273
+ }
274
+
275
+ try {
276
+ card = (await stripe.customers.createSource(
277
+ props.customer,
278
+ createParams,
279
+ )) as Stripe.Card;
280
+ } catch (error) {
281
+ if (isStripeConflictError(error) && props.adopt) {
282
+ throw new Error(
283
+ "Card adoption is not supported - cards cannot be uniquely identified for adoption",
284
+ );
285
+ } else {
286
+ throw error;
287
+ }
288
+ }
289
+ }
290
+
291
+ return this({
292
+ id: card.id,
293
+ object: card.object,
294
+ customer: props.customer,
295
+ brand: card.brand,
296
+ country: card.country || undefined,
297
+ dynamicLast4: card.dynamic_last4 || undefined,
298
+ expMonth: card.exp_month,
299
+ expYear: card.exp_year,
300
+ fingerprint: card.fingerprint || undefined,
301
+ funding: card.funding,
302
+ last4: card.last4,
303
+ name: card.name || undefined,
304
+ addressCity: card.address_city || undefined,
305
+ addressCountry: card.address_country || undefined,
306
+ addressLine1: card.address_line1 || undefined,
307
+ addressLine2: card.address_line2 || undefined,
308
+ addressState: card.address_state || undefined,
309
+ addressZip: card.address_zip || undefined,
310
+ currency: card.currency || undefined,
311
+ defaultForCurrency: card.default_for_currency || undefined,
312
+ metadata: card.metadata || undefined,
313
+ tokenizationMethod: card.tokenization_method || undefined,
314
+ });
315
+ } catch (error) {
316
+ console.error("Error creating/updating card:", error);
317
+ throw error;
318
+ }
319
+ },
320
+ );
@@ -0,0 +1,145 @@
1
+ import Stripe from "stripe";
2
+ import type { Secret } from "../secret.ts";
3
+ import { withExponentialBackoff } from "../util/retry.ts";
4
+
5
+ export interface StripeClientOptions {
6
+ apiKey?: Secret | string;
7
+ }
8
+
9
+ export function createStripeClient(options: StripeClientOptions = {}): Stripe {
10
+ let apiKey: string;
11
+
12
+ if (options.apiKey) {
13
+ apiKey =
14
+ typeof options.apiKey === "string"
15
+ ? options.apiKey
16
+ : options.apiKey.unencrypted;
17
+ } else {
18
+ const envApiKey = process.env.STRIPE_API_KEY;
19
+ if (!envApiKey) {
20
+ throw new Error(
21
+ "Stripe API key is required. Provide it via the apiKey parameter or set the STRIPE_API_KEY environment variable.",
22
+ );
23
+ }
24
+ apiKey = envApiKey;
25
+ }
26
+
27
+ const stripe = new Stripe(apiKey);
28
+ return createStripeProxy(stripe);
29
+ }
30
+
31
+ export function handleStripeDeleteError(
32
+ error: any,
33
+ resourceType: string,
34
+ resourceId?: string,
35
+ ): void {
36
+ if (error?.code === "resource_missing" || error?.status === 404) {
37
+ console.log(
38
+ `${resourceType} ${resourceId || "unknown"} not found during deletion (already deleted)`,
39
+ );
40
+ return;
41
+ }
42
+
43
+ console.error(
44
+ `Error deleting ${resourceType} ${resourceId || "unknown"}:`,
45
+ error,
46
+ );
47
+ throw error;
48
+ }
49
+
50
+ export function isStripeConflictError(error: any): boolean {
51
+ return error?.status === 409 || error?.statusCode === 409;
52
+ }
53
+
54
+ /**
55
+ * Creates a proxy that wraps Stripe API calls with retry logic
56
+ */
57
+ function createStripeProxy(stripe: Stripe): Stripe {
58
+ // Cache for proxied resources to avoid creating new proxies for the same resource
59
+ const resourceProxyCache = new Map<string, any>();
60
+
61
+ return new Proxy(stripe, {
62
+ get(target, prop, receiver) {
63
+ const value = Reflect.get(target, prop, receiver);
64
+
65
+ // If it's not a property we want to proxy, return as-is
66
+ if (
67
+ typeof prop !== "string" ||
68
+ typeof value !== "object" ||
69
+ value === null
70
+ ) {
71
+ return value;
72
+ }
73
+
74
+ // Check if this looks like a Stripe resource (has methods like create, retrieve, etc.)
75
+ const hasStripeResourceMethods =
76
+ value &&
77
+ typeof value === "object" &&
78
+ (typeof value.create === "function" ||
79
+ typeof value.retrieve === "function" ||
80
+ typeof value.list === "function" ||
81
+ typeof value.update === "function" ||
82
+ typeof value.del === "function");
83
+
84
+ if (!hasStripeResourceMethods) {
85
+ return value;
86
+ }
87
+
88
+ // Return cached proxy if it exists
89
+ if (resourceProxyCache.has(prop)) {
90
+ return resourceProxyCache.get(prop);
91
+ }
92
+
93
+ // Create a new proxy for this resource
94
+ const resourceProxy = new Proxy(value, {
95
+ get(resourceTarget, methodName, resourceReceiver) {
96
+ const method = Reflect.get(
97
+ resourceTarget,
98
+ methodName,
99
+ resourceReceiver,
100
+ );
101
+
102
+ // If it's not a function, return as-is
103
+ if (typeof method !== "function") {
104
+ return method;
105
+ }
106
+
107
+ // Wrap the method with retry logic
108
+ return (...args: any[]) =>
109
+ withStripeRetry(() => method.apply(resourceTarget, args));
110
+ },
111
+ });
112
+
113
+ // Cache the proxy
114
+ resourceProxyCache.set(prop, resourceProxy);
115
+ return resourceProxy;
116
+ },
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Determines if a Stripe error should trigger a retry
122
+ */
123
+ function isStripeRetryableError(error: any): boolean {
124
+ return (
125
+ error?.raw?.statusCode === 429 ||
126
+ error?.code === "rate_limit" ||
127
+ error?.type === "rate_limit_error"
128
+ );
129
+ }
130
+
131
+ /**
132
+ * Wraps a Stripe API operation with retry logic for rate limiting
133
+ */
134
+ async function withStripeRetry<T>(
135
+ operation: () => Promise<T>,
136
+ maxAttempts = 5,
137
+ initialDelayMs = 1000,
138
+ ): Promise<T> {
139
+ return withExponentialBackoff(
140
+ operation,
141
+ isStripeRetryableError,
142
+ maxAttempts,
143
+ initialDelayMs,
144
+ );
145
+ }