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.
- package/lib/aws/control/client.js +1 -1
- package/lib/aws/control/client.js.map +1 -1
- package/lib/aws/control/resource.d.ts.map +1 -1
- package/lib/aws/control/resource.js +2 -1
- package/lib/aws/control/resource.js.map +1 -1
- package/lib/aws/function.d.ts.map +1 -1
- package/lib/aws/function.js +1 -4
- package/lib/aws/function.js.map +1 -1
- package/lib/cloudflare/ai.d.ts +21 -0
- package/lib/cloudflare/ai.d.ts.map +1 -1
- package/lib/cloudflare/ai.js +21 -0
- package/lib/cloudflare/ai.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +3 -3
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +3 -3
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/index.d.ts +2 -0
- package/lib/cloudflare/do-state-store/index.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/index.js +2 -0
- package/lib/cloudflare/do-state-store/index.js.map +1 -0
- package/lib/cloudflare/do-state-store/internal.d.ts +19 -0
- package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/internal.js +163 -0
- package/lib/cloudflare/do-state-store/internal.js.map +1 -0
- package/lib/cloudflare/do-state-store/store.d.ts +59 -0
- package/lib/cloudflare/do-state-store/store.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/store.js +120 -0
- package/lib/cloudflare/do-state-store/store.js.map +1 -0
- package/lib/cloudflare/do-state-store/types.d.ts +82 -0
- package/lib/cloudflare/do-state-store/types.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/types.js +1 -0
- package/lib/cloudflare/do-state-store/types.js.map +1 -0
- package/lib/cloudflare/do-state-store/worker.d.ts +27 -0
- package/lib/cloudflare/do-state-store/worker.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/worker.js +170 -0
- package/lib/cloudflare/do-state-store/worker.js.map +1 -0
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/pipeline.js +1 -1
- package/lib/cloudflare/pipeline.js.map +1 -1
- package/lib/cloudflare/queue-consumer.d.ts +14 -1
- package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
- package/lib/cloudflare/queue-consumer.js +17 -0
- package/lib/cloudflare/queue-consumer.js.map +1 -1
- package/lib/cloudflare/website.d.ts +30 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +8 -5
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-assets.d.ts.map +1 -1
- package/lib/cloudflare/worker-assets.js +13 -3
- package/lib/cloudflare/worker-assets.js.map +1 -1
- package/lib/cloudflare/worker-metadata.js +1 -1
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +40 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +40 -38
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.js +1 -1
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/cloudflare/zone.d.ts +30 -2
- package/lib/cloudflare/zone.d.ts.map +1 -1
- package/lib/cloudflare/zone.js +53 -0
- package/lib/cloudflare/zone.js.map +1 -1
- package/lib/fs/file-system-state-store.d.ts +3 -1
- package/lib/fs/file-system-state-store.d.ts.map +1 -1
- package/lib/fs/file-system-state-store.js +2 -2
- package/lib/fs/file-system-state-store.js.map +1 -1
- package/lib/stripe/card.d.ts +171 -0
- package/lib/stripe/card.d.ts.map +1 -0
- package/lib/stripe/card.js +148 -0
- package/lib/stripe/card.js.map +1 -0
- package/lib/stripe/client.d.ts +9 -0
- package/lib/stripe/client.d.ts.map +1 -0
- package/lib/stripe/client.js +94 -0
- package/lib/stripe/client.js.map +1 -0
- package/lib/stripe/coupon.d.ts +135 -0
- package/lib/stripe/coupon.d.ts.map +1 -0
- package/lib/stripe/coupon.js +161 -0
- package/lib/stripe/coupon.js.map +1 -0
- package/lib/stripe/customer.d.ts +269 -0
- package/lib/stripe/customer.d.ts.map +1 -0
- package/lib/stripe/customer.js +289 -0
- package/lib/stripe/customer.js.map +1 -0
- package/lib/stripe/entitlements-feature.d.ts +84 -0
- package/lib/stripe/entitlements-feature.d.ts.map +1 -0
- package/lib/stripe/entitlements-feature.js +102 -0
- package/lib/stripe/entitlements-feature.js.map +1 -0
- package/lib/stripe/file.d.ts +143 -0
- package/lib/stripe/file.d.ts.map +1 -0
- package/lib/stripe/file.js +97 -0
- package/lib/stripe/file.js.map +1 -0
- package/lib/stripe/index.d.ts +11 -0
- package/lib/stripe/index.d.ts.map +1 -1
- package/lib/stripe/index.js +11 -0
- package/lib/stripe/index.js.map +1 -1
- package/lib/stripe/meter.d.ts +10 -1
- package/lib/stripe/meter.d.ts.map +1 -1
- package/lib/stripe/meter.js +13 -7
- package/lib/stripe/meter.js.map +1 -1
- package/lib/stripe/portal-configuration.d.ts +286 -0
- package/lib/stripe/portal-configuration.d.ts.map +1 -0
- package/lib/stripe/portal-configuration.js +320 -0
- package/lib/stripe/portal-configuration.js.map +1 -0
- package/lib/stripe/price.d.ts +10 -1
- package/lib/stripe/price.d.ts.map +1 -1
- package/lib/stripe/price.js +34 -13
- package/lib/stripe/price.js.map +1 -1
- package/lib/stripe/product-feature.d.ts +76 -0
- package/lib/stripe/product-feature.d.ts.map +1 -0
- package/lib/stripe/product-feature.js +74 -0
- package/lib/stripe/product-feature.js.map +1 -0
- package/lib/stripe/product.d.ts +10 -1
- package/lib/stripe/product.d.ts.map +1 -1
- package/lib/stripe/product.js +33 -14
- package/lib/stripe/product.js.map +1 -1
- package/lib/stripe/promotion-code.d.ts +138 -0
- package/lib/stripe/promotion-code.d.ts.map +1 -0
- package/lib/stripe/promotion-code.js +150 -0
- package/lib/stripe/promotion-code.js.map +1 -0
- package/lib/stripe/shipping-rate.d.ts +188 -0
- package/lib/stripe/shipping-rate.d.ts.map +1 -0
- package/lib/stripe/shipping-rate.js +198 -0
- package/lib/stripe/shipping-rate.js.map +1 -0
- package/lib/stripe/tax-rate.d.ts +130 -0
- package/lib/stripe/tax-rate.d.ts.map +1 -0
- package/lib/stripe/tax-rate.js +126 -0
- package/lib/stripe/tax-rate.js.map +1 -0
- package/lib/stripe/webhook.d.ts +10 -1
- package/lib/stripe/webhook.d.ts.map +1 -1
- package/lib/stripe/webhook.js +59 -14
- package/lib/stripe/webhook.js.map +1 -1
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +2 -6
- package/lib/test/bun.js.map +1 -1
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +3 -7
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/retry.d.ts +1 -1
- package/lib/util/retry.d.ts.map +1 -1
- package/lib/util/retry.js.map +1 -1
- package/package.json +3 -2
- package/src/aws/control/client.ts +1 -1
- package/src/aws/control/resource.ts +2 -1
- package/src/aws/function.ts +3 -6
- package/src/cloudflare/ai.ts +21 -0
- package/src/cloudflare/bindings.ts +3 -2
- package/src/cloudflare/bound.ts +3 -3
- package/src/cloudflare/do-state-store/index.ts +1 -0
- package/src/cloudflare/do-state-store/internal.ts +215 -0
- package/src/cloudflare/do-state-store/store.ts +188 -0
- package/src/cloudflare/do-state-store/types.ts +74 -0
- package/src/cloudflare/do-state-store/worker.ts +199 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/pipeline.ts +1 -1
- package/src/cloudflare/queue-consumer.ts +59 -1
- package/src/cloudflare/website.ts +38 -6
- package/src/cloudflare/worker-assets.ts +15 -3
- package/src/cloudflare/worker-metadata.ts +1 -1
- package/src/cloudflare/worker.ts +66 -46
- package/src/cloudflare/wrangler.json.ts +1 -1
- package/src/cloudflare/zone.ts +75 -2
- package/src/fs/file-system-state-store.ts +7 -2
- package/src/stripe/card.ts +320 -0
- package/src/stripe/client.ts +145 -0
- package/src/stripe/coupon.ts +280 -0
- package/src/stripe/customer.ts +528 -0
- package/src/stripe/entitlements-feature.ts +169 -0
- package/src/stripe/file.ts +219 -0
- package/src/stripe/index.ts +11 -0
- package/src/stripe/meter.ts +23 -7
- package/src/stripe/portal-configuration.ts +609 -0
- package/src/stripe/price.ts +47 -14
- package/src/stripe/product-feature.ts +150 -0
- package/src/stripe/product.ts +48 -15
- package/src/stripe/promotion-code.ts +268 -0
- package/src/stripe/shipping-rate.ts +362 -0
- package/src/stripe/tax-rate.ts +218 -0
- package/src/stripe/webhook.ts +82 -15
- package/src/test/bun.ts +2 -7
- package/src/test/vitest.ts +3 -8
- package/src/util/retry.ts +1 -1
package/src/cloudflare/zone.ts
CHANGED
|
@@ -165,9 +165,9 @@ export interface ZoneProps extends CloudflareApiOptions {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
|
-
*
|
|
168
|
+
* Zone data structure (used for lookup functions)
|
|
169
169
|
*/
|
|
170
|
-
export interface
|
|
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(
|
|
15
|
-
|
|
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
|
+
}
|