alchemy 0.24.0 → 0.24.1

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 (163) hide show
  1. package/lib/aws/function.d.ts.map +1 -1
  2. package/lib/aws/function.js +1 -4
  3. package/lib/aws/function.js.map +1 -1
  4. package/lib/cloudflare/bindings.d.ts +3 -3
  5. package/lib/cloudflare/bindings.d.ts.map +1 -1
  6. package/lib/cloudflare/bindings.js.map +1 -1
  7. package/lib/cloudflare/bound.d.ts +3 -3
  8. package/lib/cloudflare/bound.d.ts.map +1 -1
  9. package/lib/cloudflare/do-state-store/index.d.ts +2 -0
  10. package/lib/cloudflare/do-state-store/index.d.ts.map +1 -0
  11. package/lib/cloudflare/do-state-store/index.js +2 -0
  12. package/lib/cloudflare/do-state-store/index.js.map +1 -0
  13. package/lib/cloudflare/do-state-store/internal.d.ts +19 -0
  14. package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -0
  15. package/lib/cloudflare/do-state-store/internal.js +163 -0
  16. package/lib/cloudflare/do-state-store/internal.js.map +1 -0
  17. package/lib/cloudflare/do-state-store/store.d.ts +59 -0
  18. package/lib/cloudflare/do-state-store/store.d.ts.map +1 -0
  19. package/lib/cloudflare/do-state-store/store.js +120 -0
  20. package/lib/cloudflare/do-state-store/store.js.map +1 -0
  21. package/lib/cloudflare/do-state-store/types.d.ts +82 -0
  22. package/lib/cloudflare/do-state-store/types.d.ts.map +1 -0
  23. package/lib/cloudflare/do-state-store/types.js +1 -0
  24. package/lib/cloudflare/do-state-store/types.js.map +1 -0
  25. package/lib/cloudflare/do-state-store/worker.d.ts +27 -0
  26. package/lib/cloudflare/do-state-store/worker.d.ts.map +1 -0
  27. package/lib/cloudflare/do-state-store/worker.js +170 -0
  28. package/lib/cloudflare/do-state-store/worker.js.map +1 -0
  29. package/lib/cloudflare/index.d.ts +1 -0
  30. package/lib/cloudflare/index.d.ts.map +1 -1
  31. package/lib/cloudflare/index.js +1 -0
  32. package/lib/cloudflare/index.js.map +1 -1
  33. package/lib/cloudflare/pipeline.js +1 -1
  34. package/lib/cloudflare/pipeline.js.map +1 -1
  35. package/lib/cloudflare/queue-consumer.d.ts +14 -1
  36. package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
  37. package/lib/cloudflare/queue-consumer.js +17 -0
  38. package/lib/cloudflare/queue-consumer.js.map +1 -1
  39. package/lib/cloudflare/website.d.ts +30 -1
  40. package/lib/cloudflare/website.d.ts.map +1 -1
  41. package/lib/cloudflare/website.js +8 -5
  42. package/lib/cloudflare/website.js.map +1 -1
  43. package/lib/cloudflare/worker-metadata.js +1 -1
  44. package/lib/cloudflare/worker-metadata.js.map +1 -1
  45. package/lib/cloudflare/worker.d.ts +40 -1
  46. package/lib/cloudflare/worker.d.ts.map +1 -1
  47. package/lib/cloudflare/worker.js +40 -38
  48. package/lib/cloudflare/worker.js.map +1 -1
  49. package/lib/cloudflare/wrangler.json.js +1 -1
  50. package/lib/cloudflare/wrangler.json.js.map +1 -1
  51. package/lib/fs/file-system-state-store.d.ts +3 -1
  52. package/lib/fs/file-system-state-store.d.ts.map +1 -1
  53. package/lib/fs/file-system-state-store.js +2 -2
  54. package/lib/fs/file-system-state-store.js.map +1 -1
  55. package/lib/stripe/card.d.ts +171 -0
  56. package/lib/stripe/card.d.ts.map +1 -0
  57. package/lib/stripe/card.js +148 -0
  58. package/lib/stripe/card.js.map +1 -0
  59. package/lib/stripe/client.d.ts +9 -0
  60. package/lib/stripe/client.d.ts.map +1 -0
  61. package/lib/stripe/client.js +94 -0
  62. package/lib/stripe/client.js.map +1 -0
  63. package/lib/stripe/coupon.d.ts +135 -0
  64. package/lib/stripe/coupon.d.ts.map +1 -0
  65. package/lib/stripe/coupon.js +161 -0
  66. package/lib/stripe/coupon.js.map +1 -0
  67. package/lib/stripe/customer.d.ts +269 -0
  68. package/lib/stripe/customer.d.ts.map +1 -0
  69. package/lib/stripe/customer.js +289 -0
  70. package/lib/stripe/customer.js.map +1 -0
  71. package/lib/stripe/entitlements-feature.d.ts +84 -0
  72. package/lib/stripe/entitlements-feature.d.ts.map +1 -0
  73. package/lib/stripe/entitlements-feature.js +102 -0
  74. package/lib/stripe/entitlements-feature.js.map +1 -0
  75. package/lib/stripe/file.d.ts +143 -0
  76. package/lib/stripe/file.d.ts.map +1 -0
  77. package/lib/stripe/file.js +97 -0
  78. package/lib/stripe/file.js.map +1 -0
  79. package/lib/stripe/index.d.ts +11 -0
  80. package/lib/stripe/index.d.ts.map +1 -1
  81. package/lib/stripe/index.js +11 -0
  82. package/lib/stripe/index.js.map +1 -1
  83. package/lib/stripe/meter.d.ts +10 -1
  84. package/lib/stripe/meter.d.ts.map +1 -1
  85. package/lib/stripe/meter.js +13 -7
  86. package/lib/stripe/meter.js.map +1 -1
  87. package/lib/stripe/portal-configuration.d.ts +286 -0
  88. package/lib/stripe/portal-configuration.d.ts.map +1 -0
  89. package/lib/stripe/portal-configuration.js +320 -0
  90. package/lib/stripe/portal-configuration.js.map +1 -0
  91. package/lib/stripe/price.d.ts +10 -1
  92. package/lib/stripe/price.d.ts.map +1 -1
  93. package/lib/stripe/price.js +34 -13
  94. package/lib/stripe/price.js.map +1 -1
  95. package/lib/stripe/product-feature.d.ts +76 -0
  96. package/lib/stripe/product-feature.d.ts.map +1 -0
  97. package/lib/stripe/product-feature.js +74 -0
  98. package/lib/stripe/product-feature.js.map +1 -0
  99. package/lib/stripe/product.d.ts +10 -1
  100. package/lib/stripe/product.d.ts.map +1 -1
  101. package/lib/stripe/product.js +33 -14
  102. package/lib/stripe/product.js.map +1 -1
  103. package/lib/stripe/promotion-code.d.ts +138 -0
  104. package/lib/stripe/promotion-code.d.ts.map +1 -0
  105. package/lib/stripe/promotion-code.js +150 -0
  106. package/lib/stripe/promotion-code.js.map +1 -0
  107. package/lib/stripe/shipping-rate.d.ts +188 -0
  108. package/lib/stripe/shipping-rate.d.ts.map +1 -0
  109. package/lib/stripe/shipping-rate.js +198 -0
  110. package/lib/stripe/shipping-rate.js.map +1 -0
  111. package/lib/stripe/tax-rate.d.ts +130 -0
  112. package/lib/stripe/tax-rate.d.ts.map +1 -0
  113. package/lib/stripe/tax-rate.js +126 -0
  114. package/lib/stripe/tax-rate.js.map +1 -0
  115. package/lib/stripe/webhook.d.ts +10 -1
  116. package/lib/stripe/webhook.d.ts.map +1 -1
  117. package/lib/stripe/webhook.js +59 -14
  118. package/lib/stripe/webhook.js.map +1 -1
  119. package/lib/test/bun.d.ts.map +1 -1
  120. package/lib/test/bun.js +2 -6
  121. package/lib/test/bun.js.map +1 -1
  122. package/lib/test/vitest.d.ts.map +1 -1
  123. package/lib/test/vitest.js +3 -7
  124. package/lib/test/vitest.js.map +1 -1
  125. package/lib/util/retry.d.ts +1 -1
  126. package/lib/util/retry.d.ts.map +1 -1
  127. package/lib/util/retry.js.map +1 -1
  128. package/package.json +2 -1
  129. package/src/aws/function.ts +3 -6
  130. package/src/cloudflare/bindings.ts +3 -2
  131. package/src/cloudflare/bound.ts +3 -3
  132. package/src/cloudflare/do-state-store/index.ts +1 -0
  133. package/src/cloudflare/do-state-store/internal.ts +215 -0
  134. package/src/cloudflare/do-state-store/store.ts +188 -0
  135. package/src/cloudflare/do-state-store/types.ts +74 -0
  136. package/src/cloudflare/do-state-store/worker.ts +199 -0
  137. package/src/cloudflare/index.ts +1 -0
  138. package/src/cloudflare/pipeline.ts +1 -1
  139. package/src/cloudflare/queue-consumer.ts +59 -1
  140. package/src/cloudflare/website.ts +38 -6
  141. package/src/cloudflare/worker-metadata.ts +1 -1
  142. package/src/cloudflare/worker.ts +66 -46
  143. package/src/cloudflare/wrangler.json.ts +1 -1
  144. package/src/fs/file-system-state-store.ts +7 -2
  145. package/src/stripe/card.ts +320 -0
  146. package/src/stripe/client.ts +145 -0
  147. package/src/stripe/coupon.ts +280 -0
  148. package/src/stripe/customer.ts +528 -0
  149. package/src/stripe/entitlements-feature.ts +169 -0
  150. package/src/stripe/file.ts +219 -0
  151. package/src/stripe/index.ts +11 -0
  152. package/src/stripe/meter.ts +23 -7
  153. package/src/stripe/portal-configuration.ts +609 -0
  154. package/src/stripe/price.ts +47 -14
  155. package/src/stripe/product-feature.ts +150 -0
  156. package/src/stripe/product.ts +48 -15
  157. package/src/stripe/promotion-code.ts +268 -0
  158. package/src/stripe/shipping-rate.ts +362 -0
  159. package/src/stripe/tax-rate.ts +218 -0
  160. package/src/stripe/webhook.ts +82 -15
  161. package/src/test/bun.ts +2 -7
  162. package/src/test/vitest.ts +3 -8
  163. package/src/util/retry.ts +1 -1
@@ -6,7 +6,7 @@ import {
6
6
  type CloudflareApi,
7
7
  type CloudflareApiOptions,
8
8
  } from "./api.ts";
9
- import type { QueueResource, Queue } from "./queue.ts";
9
+ import type { Queue, QueueResource } from "./queue.ts";
10
10
 
11
11
  /**
12
12
  * Settings for configuring a Queue Consumer
@@ -450,3 +450,61 @@ export async function listQueueConsumers(
450
450
  : undefined,
451
451
  }));
452
452
  }
453
+
454
+ export async function listQueueConsumersForWorker(
455
+ api: CloudflareApi,
456
+ workerName: string,
457
+ ) {
458
+ const response = await api.get(
459
+ `/accounts/${api.accountId}/workers/scripts/${workerName}/queue-consumers?perPage=100`,
460
+ );
461
+
462
+ if (response.status === 404) {
463
+ return [];
464
+ }
465
+
466
+ if (!response.ok) {
467
+ return await handleApiError(
468
+ response,
469
+ "list",
470
+ "QueueConsumer",
471
+ `for worker ${workerName}`,
472
+ );
473
+ }
474
+
475
+ const data = (await response.json()) as {
476
+ result: Array<{
477
+ script: string;
478
+ settings?: {
479
+ batch_size?: number;
480
+ max_retries?: number;
481
+ max_wait_time_ms?: number;
482
+ retry_delay?: number;
483
+ dead_letter_queue?: string;
484
+ };
485
+ type: string;
486
+ queue_name: string;
487
+ queue_id: string;
488
+ consumer_id: string;
489
+ created_on: string;
490
+ }>;
491
+ success: boolean;
492
+ errors: any[] | null;
493
+ messages: any[] | null;
494
+ result_info: {
495
+ page: number;
496
+ per_page: number;
497
+ count: number;
498
+ total_count: number;
499
+ total_pages: number;
500
+ };
501
+ };
502
+
503
+ return data.result.map((consumer) => ({
504
+ queueName: consumer.queue_name,
505
+ queueId: consumer.queue_id,
506
+ consumerId: consumer.consumer_id,
507
+ createdOn: consumer.created_on,
508
+ settings: consumer.settings,
509
+ }));
510
+ }
@@ -15,8 +15,37 @@ export interface WebsiteProps<B extends Bindings>
15
15
  extends Omit<WorkerProps<B>, "name" | "assets" | "entrypoint"> {
16
16
  /**
17
17
  * The command to run to build the site
18
+ *
19
+ * If one is not provided, the build is assumed to have already happened.
20
+ */
21
+ command?: string;
22
+
23
+ /**
24
+ * Whether to memoize the command (only re-run if the command changes)
25
+ *
26
+ * When set to `true`, the command will only be re-executed if the command string changes.
27
+ *
28
+ * When set to an object with `patterns`, the command will be re-executed if either:
29
+ * 1. The command string changes, or
30
+ * 2. The contents of any files matching the glob patterns change
31
+ *
32
+ * ⚠️ **Important Note**: When using memoization with build commands, the build outputs
33
+ * will not be produced if the command is memoized. This is because the command is not
34
+ * actually executed when memoized. Consider disabling memoization in CI environments:
35
+ *
36
+ * @example
37
+ * // Disable memoization in CI to ensure build outputs are always produced
38
+ * await Website("my-website", {
39
+ * command: "vite build",
40
+ * memoize: process.env.CI ? false : {
41
+ * patterns: ["./src/**"]
42
+ * }
43
+ * });
44
+ *
45
+ * @default false
18
46
  */
19
- command: string;
47
+ memoize?: boolean | { patterns: string[] };
48
+
20
49
  /**
21
50
  * The name of the worker
22
51
  *
@@ -128,11 +157,14 @@ export default {
128
157
  });
129
158
  }
130
159
 
131
- await Exec("build", {
132
- cwd,
133
- command: props.command,
134
- env: props.env,
135
- });
160
+ if (props.command) {
161
+ await Exec("build", {
162
+ cwd,
163
+ command: props.command,
164
+ env: props.env,
165
+ memoize: props.memoize,
166
+ });
167
+ }
136
168
 
137
169
  return (await Worker("worker", {
138
170
  ...workerProps,
@@ -361,7 +361,7 @@ export async function prepareWorkerMetadata<B extends Bindings>(
361
361
  meta.bindings.push({
362
362
  type: "service",
363
363
  name: bindingName,
364
- service: binding.name,
364
+ service: "service" in binding ? binding.service : binding.name,
365
365
  });
366
366
  } else if (binding.type === "durable_object_namespace") {
367
367
  meta.bindings.push({
@@ -23,6 +23,7 @@ import {
23
23
  type Binding,
24
24
  type Bindings,
25
25
  Json,
26
+ type WorkerBindingService,
26
27
  type WorkerBindingSpec,
27
28
  } from "./bindings.ts";
28
29
  import type { Bound } from "./bound.ts";
@@ -46,7 +47,7 @@ import { isPipeline } from "./pipeline.ts";
46
47
  import {
47
48
  QueueConsumer,
48
49
  deleteQueueConsumer,
49
- listQueueConsumers,
50
+ listQueueConsumersForWorker,
50
51
  } from "./queue-consumer.ts";
51
52
  import { type QueueResource, isQueue } from "./queue.ts";
52
53
  import { isVectorizeIndex } from "./vectorize-index.ts";
@@ -384,6 +385,56 @@ export type Worker<
384
385
  compatibilityFlags: string[];
385
386
  };
386
387
 
388
+ /**
389
+ * Represents a reference to a Cloudflare Worker service.
390
+ *
391
+ * @template RPC - The type of the worker's RPC entrypoint, defaults to Rpc.WorkerEntrypointBranded.
392
+ *
393
+ * This interface extends all properties of WorkerBindingService except for "name".
394
+ * It also includes an optional __rpc__ property for type branding.
395
+ */
396
+ export type WorkerRef<
397
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
398
+ > = Omit<WorkerBindingService, "name"> & {
399
+ /**
400
+ * Optional type branding for the worker's RPC entrypoint.
401
+ */
402
+ __rpc__?: RPC;
403
+ };
404
+
405
+ /**
406
+ * Creates a reference to a Cloudflare Worker service.
407
+ *
408
+ * @example
409
+ * // Create a reference to a Cloudflare Worker service:
410
+ * const ref = WorkerRef({
411
+ * service: "my-worker",
412
+ * environment: "production",
413
+ * namespace: "main"
414
+ * });
415
+ *
416
+ * // Optionally, you can specify only the service:
417
+ * const ref2 = WorkerRef({ service: "my-worker" });
418
+ *
419
+ * // You can also specify the RPC type for stronger typing:
420
+ * interface MyWorkerRPC extends Rpc.WorkerEntrypointBranded {
421
+ * myMethod(arg: string): Promise<number>;
422
+ * }
423
+ * const typedRef = WorkerRef<MyWorkerRPC>({ service: "my-worker" });
424
+ */
425
+ export function WorkerRef<
426
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
427
+ >(options?: {
428
+ service: string;
429
+ environment?: string;
430
+ namespace?: string;
431
+ }): WorkerRef<RPC> {
432
+ return {
433
+ ...options,
434
+ type: "service",
435
+ } as WorkerRef<RPC>;
436
+ }
437
+
387
438
  /**
388
439
  * A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
389
440
  *
@@ -869,7 +920,7 @@ export const _Worker = Resource(
869
920
 
870
921
  if (this.phase === "delete") {
871
922
  // Delete any queue consumers attached to this worker first
872
- await deleteQueueConsumers(this, api, workerName);
923
+ await deleteQueueConsumers(api, workerName);
873
924
 
874
925
  // @ts-ignore
875
926
  await uploadWorkerScript({
@@ -879,7 +930,7 @@ export const _Worker = Resource(
879
930
  script:
880
931
  props.format === "cjs"
881
932
  ? "addEventListener('fetch', event => { event.respondWith(new Response('hello world')); });"
882
- : "export default { fetch(request) { return new Response('hello world'); } }",
933
+ : "export default { fetch(request) { return new Response('hello world'); }, queue: () => {} }",
883
934
  bindings: {} as B,
884
935
  // we are writing a stub worker (to remove binding/event source dependencies)
885
936
  // queue consumers will no longer exist by this point
@@ -1074,7 +1125,14 @@ export async function putWorker(
1074
1125
 
1075
1126
  return formData;
1076
1127
  },
1077
- (err) => err.status === 404 || err.status === 500 || err.status === 503,
1128
+ (err) =>
1129
+ err.status === 404 ||
1130
+ err.status === 500 ||
1131
+ err.status === 503 ||
1132
+ // this is a tranient error that cloudflare throws randomly
1133
+ (err instanceof CloudflareApiError &&
1134
+ err.status === 400 &&
1135
+ err.message.match(/binding.*failed to generate/)),
1078
1136
  10,
1079
1137
  100,
1080
1138
  );
@@ -1248,53 +1306,15 @@ async function getWorkerBindings(api: CloudflareApi, workerName: string) {
1248
1306
  * @param api CloudflareApi instance
1249
1307
  * @param workerName Name of the worker script
1250
1308
  */
1251
- async function deleteQueueConsumers<B extends Bindings>(
1252
- ctx: Context<Worker<B>>,
1309
+ async function deleteQueueConsumers(
1253
1310
  api: CloudflareApi,
1254
1311
  workerName: string,
1255
1312
  ): Promise<void> {
1256
- const eventSources = ctx.output?.eventSources || [];
1257
-
1258
- // Extract queue IDs from event sources
1259
- const queueIds = eventSources.flatMap((eventSource) => {
1260
- if (isQueue(eventSource)) {
1261
- return [eventSource.id];
1262
- }
1263
- if (isQueueEventSource(eventSource)) {
1264
- return [eventSource.queue.id];
1265
- }
1266
- return [];
1267
- });
1313
+ const consumers = await listQueueConsumersForWorker(api, workerName);
1268
1314
 
1269
- // Process each queue associated with this worker
1270
1315
  await Promise.all(
1271
- queueIds.map(async (queueId) => {
1272
- try {
1273
- // List all consumers for this queue
1274
- const consumers = await listQueueConsumers(api, queueId);
1275
-
1276
- // Filter consumers by worker name
1277
- const workerConsumers = consumers.filter(
1278
- (consumer) => consumer.scriptName === workerName,
1279
- );
1280
-
1281
- // Delete all consumers for this worker in parallel
1282
- await Promise.all(
1283
- workerConsumers.map(async (consumer) => {
1284
- console.log(
1285
- `Deleting queue consumer ${consumer.id} for worker ${workerName}`,
1286
- );
1287
- // Use the deleteQueueConsumer function from queue-consumer.ts
1288
- await deleteQueueConsumer(api, consumer.queueId, consumer.id);
1289
- }),
1290
- );
1291
- } catch (err) {
1292
- if (err instanceof CloudflareApiError && err.status === 404) {
1293
- // this is OK
1294
- } else {
1295
- throw err;
1296
- }
1297
- }
1316
+ consumers.map(async (consumer) => {
1317
+ await deleteQueueConsumer(api, consumer.queueId, consumer.consumerId);
1298
1318
  }),
1299
1319
  );
1300
1320
  }
@@ -447,7 +447,7 @@ function processBindings(
447
447
  // Service binding
448
448
  services.push({
449
449
  binding: bindingName,
450
- service: binding.name,
450
+ service: "name" in binding ? binding.name : binding.service,
451
451
  });
452
452
  } else if (binding.type === "kv_namespace") {
453
453
  // KV Namespace binding
@@ -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
+ );