@happyvertical/payments 0.80.0 → 0.80.2

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.
@@ -1,1008 +1,605 @@
1
+ import { a as PaymentVerificationError, r as PaymentProviderError, t as PaymentConfigurationError } from "../chunks/errors-feZrwnTy.js";
2
+ import { _ as normalizeUrlString, a as formEncode, b as rememberPaymentOption, f as normalizeMaxStoredPaymentOptions, g as normalizePositivePaymentAmount, h as normalizePositiveMinorUnitAmount, m as normalizeNonEmptyString, o as getFetch, p as normalizeMinorUnitAmount, t as applyExpiryToPendingStatus, u as normalizeDate, v as pollPaymentStatus, y as readJsonResponse } from "../chunks/shared-DVl6UzD9.js";
1
3
  import { createHmac, timingSafeEqual } from "node:crypto";
2
- import { P as PaymentConfigurationError, b as PaymentProviderError, d as PaymentVerificationError } from "../chunks/errors-BgFC46qQ.js";
3
- import { g as getFetch, a as normalizeMaxStoredPaymentOptions, b as normalizeUrlString, n as normalizeNonEmptyString, o as normalizePositivePaymentAmount, h as normalizeDate, t as formEncode, r as rememberPaymentOption, p as pollPaymentStatus, i as normalizeMinorUnitAmount, e as normalizePositiveMinorUnitAmount, j as readJsonResponse, s as applyExpiryToPendingStatus } from "../chunks/shared-DGHSqDQT.js";
4
- const STRIPE_BACKEND_ID = "stripe";
5
- const STRIPE_CHECKOUT_MIN_EXPIRY_MS = 30 * 60 * 1e3;
6
- const STRIPE_CHECKOUT_MAX_EXPIRY_MS = 24 * 60 * 60 * 1e3;
7
- const DEFAULT_STRIPE_API_VERSION = "2024-06-20";
8
- const DEFAULT_MAX_STORED_WEBHOOK_EVENT_IDS = 5e4;
9
- class StripeAdapter {
10
- constructor(options) {
11
- this.options = options;
12
- if (typeof options.secretKey !== "string" || !options.secretKey.trim()) {
13
- throw new PaymentConfigurationError("StripeAdapter requires secretKey.");
14
- }
15
- if (options.webhookSecret !== void 0) {
16
- if (typeof options.webhookSecret !== "string") {
17
- throw new PaymentConfigurationError(
18
- "StripeAdapter webhookSecret must be a string when configured."
19
- );
20
- }
21
- if (!options.webhookSecret.trim()) {
22
- throw new PaymentConfigurationError(
23
- "StripeAdapter webhookSecret must not be empty when configured."
24
- );
25
- }
26
- }
27
- if (options.apiBaseUrl !== void 0) {
28
- if (typeof options.apiBaseUrl !== "string") {
29
- throw new PaymentConfigurationError(
30
- "StripeAdapter apiBaseUrl must be a string when configured."
31
- );
32
- }
33
- if (!options.apiBaseUrl.trim()) {
34
- throw new PaymentConfigurationError(
35
- "StripeAdapter apiBaseUrl must not be empty when configured."
36
- );
37
- }
38
- }
39
- this.fetch = getFetch(options.fetch);
40
- this.maxStoredPaymentOptions = normalizeMaxStoredPaymentOptions(
41
- options.maxStoredPaymentOptions,
42
- "StripeAdapter maxStoredPaymentOptions"
43
- );
44
- this.maxStoredWebhookEventIds = normalizeMaxStoredPaymentOptions(
45
- options.maxStoredWebhookEventIds ?? DEFAULT_MAX_STORED_WEBHOOK_EVENT_IDS,
46
- "StripeAdapter maxStoredWebhookEventIds"
47
- );
48
- this.secretKey = options.secretKey.trim();
49
- this.apiBaseUrl = normalizeUrlString(
50
- options.apiBaseUrl ?? "https://api.stripe.com/v1",
51
- "StripeAdapter apiBaseUrl"
52
- ).replace(/\/$/, "");
53
- this.apiVersion = normalizeNonEmptyString(
54
- options.apiVersion ?? DEFAULT_STRIPE_API_VERSION,
55
- "StripeAdapter apiVersion"
56
- );
57
- this.defaultCurrency = normalizeStripeCurrency(
58
- options.defaultCurrency ?? "usd"
59
- );
60
- this.supportedCurrencies = normalizeSupportedStripeCurrencies(
61
- options.supportedCurrencies,
62
- this.defaultCurrency
63
- );
64
- this.checkoutMode = options.checkoutMode ?? "payment";
65
- if (this.checkoutMode !== "payment") {
66
- throw new PaymentConfigurationError(
67
- "StripeAdapter only supports payment Checkout mode."
68
- );
69
- }
70
- this.webhookSecret = options.webhookSecret?.trim();
71
- this.capabilities = {
72
- id: STRIPE_BACKEND_ID,
73
- displayName: "Stripe Checkout",
74
- settlementCurrency: this.defaultCurrency.toUpperCase(),
75
- supportedSettlementCurrencies: [...this.supportedCurrencies].map(
76
- (currency) => currency.toUpperCase()
77
- ),
78
- chain: "stripe",
79
- settlementShape: "url",
80
- x402Capable: false,
81
- confirmationLatency: {
82
- expectedSeconds: 5,
83
- maxExpectedSeconds: 600,
84
- description: "Card payments usually confirm in seconds; bank debits can remain processing while Stripe settles them."
85
- },
86
- supportsRefunds: true,
87
- supportsPayouts: true,
88
- supportsWebhooks: true,
89
- supportsManualCapture: true,
90
- supportsSavedPaymentMethods: true,
91
- metadata: {
92
- provider: "stripe"
93
- }
94
- };
95
- }
96
- capabilities;
97
- fetch;
98
- secretKey;
99
- apiBaseUrl;
100
- apiVersion;
101
- defaultCurrency;
102
- supportedCurrencies;
103
- checkoutMode;
104
- webhookSecret;
105
- maxStoredPaymentOptions;
106
- maxStoredWebhookEventIds;
107
- optionsByQuote = /* @__PURE__ */ new Map();
108
- seenWebhookEventIds = /* @__PURE__ */ new Set();
109
- async createPaymentOption(input) {
110
- const quoteId = normalizeNonEmptyString(input.quoteId, "Stripe quoteId");
111
- const rawSuccessUrl = input.successUrl ?? this.options.successUrl;
112
- const rawCancelUrl = input.cancelUrl ?? this.options.cancelUrl;
113
- if (rawSuccessUrl === void 0 || rawCancelUrl === void 0) {
114
- throw new PaymentConfigurationError(
115
- "StripeAdapter createPaymentOption requires successUrl and cancelUrl."
116
- );
117
- }
118
- const successUrl = normalizeUrlString(rawSuccessUrl, "Stripe successUrl");
119
- const cancelUrl = normalizeUrlString(rawCancelUrl, "Stripe cancelUrl");
120
- const currency = normalizeStripeCurrency(input.currency);
121
- this.assertSupportedCurrency(currency);
122
- const amount = normalizePositivePaymentAmount(
123
- input.amount,
124
- currency,
125
- "Stripe Checkout amount"
126
- );
127
- const expiresAt = normalizeDate(input.expiresAt);
128
- const stripeExpiresAt = normalizeStripeCheckoutExpiresAt(expiresAt);
129
- const params = {
130
- mode: this.checkoutMode,
131
- ...Object.fromEntries([
132
- ["success_url", successUrl],
133
- ["cancel_url", cancelUrl],
134
- ["client_reference_id", quoteId],
135
- ["customer_email", input.buyerEmail],
136
- ["expires_at", Math.floor(stripeExpiresAt.getTime() / 1e3)]
137
- ]),
138
- "line_items[0][quantity]": 1,
139
- "line_items[0][price_data][currency]": currency,
140
- "line_items[0][price_data][unit_amount]": amount,
141
- "line_items[0][price_data][product_data][name]": input.description ?? `Quote ${quoteId}`,
142
- ...flattenStripeMetadata(input.metadata),
143
- "metadata[quoteId]": quoteId
144
- };
145
- const session = await this.stripeRequest(
146
- "/checkout/sessions",
147
- {
148
- method: "POST",
149
- idempotencyKey: input.idempotencyKey,
150
- body: formEncode(params)
151
- }
152
- );
153
- const sessionId = normalizeOptionalProviderString(
154
- readProviderString(session, "id")
155
- );
156
- const checkoutUrl = normalizeOptionalProviderString(
157
- readProviderString(session, "url")
158
- );
159
- if (!sessionId || !checkoutUrl) {
160
- throw new PaymentProviderError(
161
- "Stripe Checkout Session response did not include id and url."
162
- );
163
- }
164
- const option = {
165
- backendId: this.capabilities.id,
166
- quoteId,
167
- payTo: checkoutUrl,
168
- settlementShape: "url",
169
- settlementCurrency: currency.toUpperCase(),
170
- settlementAmount: amount,
171
- amount,
172
- currency: currency.toUpperCase(),
173
- expiresAt: stripeExpiresAt,
174
- providerPaymentId: sessionId,
175
- paymentUri: checkoutUrl,
176
- metadata: {
177
- sessionId,
178
- paymentIntent: normalizeOptionalProviderString(
179
- readProviderString(session, "payment_intent")
180
- )
181
- }
182
- };
183
- rememberPaymentOption(
184
- this.optionsByQuote,
185
- quoteId,
186
- option,
187
- this.maxStoredPaymentOptions
188
- );
189
- return option;
190
- }
191
- watchPayment(input) {
192
- return pollPaymentStatus(
193
- {
194
- ...input,
195
- pollIntervalMs: input.pollIntervalMs ?? this.options.pollIntervalMs ?? 5e3
196
- },
197
- () => this.getStatus(input.quoteId, input.payTo, input.statusContext)
198
- );
199
- }
200
- async getStatus(quoteId, payTo, context = {}) {
201
- const normalizedQuoteId = normalizeNonEmptyString(
202
- quoteId,
203
- "Stripe quoteId"
204
- );
205
- const normalizedPayTo = normalizeNonEmptyString(payTo, "Stripe payTo");
206
- const contextProviderPaymentId = context.providerPaymentId === void 0 ? void 0 : normalizeNonEmptyString(
207
- context.providerPaymentId,
208
- "Stripe providerPaymentId"
209
- );
210
- const sessionId = this.optionsByQuote.get(normalizedQuoteId)?.providerPaymentId ?? contextProviderPaymentId ?? extractStripeSessionId(normalizedPayTo);
211
- if (!sessionId) {
212
- throw new PaymentConfigurationError(
213
- "Stripe getStatus requires a Checkout Session id or URL."
214
- );
215
- }
216
- const session = await this.stripeRequest(
217
- `/checkout/sessions/${encodeURIComponent(sessionId)}`
218
- );
219
- const sessionQuoteId = readStripeSessionQuoteId(session);
220
- if (sessionQuoteId !== void 0 && sessionQuoteId !== normalizedQuoteId) {
221
- throw new PaymentVerificationError(
222
- `Stripe Checkout Session quoteId ${sessionQuoteId} did not match ${normalizedQuoteId}.`
223
- );
224
- }
225
- const paymentStatus = readString(session, "payment_status");
226
- const status = mapStripeStatus(
227
- readString(session, "status"),
228
- paymentStatus
229
- );
230
- const option = this.optionsByQuote.get(normalizedQuoteId);
231
- const contextAmount = context.amount === void 0 ? void 0 : normalizeMinorUnitAmount(context.amount, "Stripe status");
232
- const contextCurrency = context.currency === void 0 ? void 0 : normalizeStripeCurrency(context.currency).toUpperCase();
233
- const sessionCurrency = readString(session, "currency") === void 0 ? void 0 : normalizeStripeCurrency(
234
- readString(session, "currency") ?? ""
235
- ).toUpperCase();
236
- const sessionAmount = readSafeInteger(session, "amount_total");
237
- const expiresAt = option?.expiresAt ?? (context.expiresAt === void 0 ? void 0 : normalizeDate(context.expiresAt));
238
- const effectiveStatus = applyExpiryToPendingStatus(status, expiresAt);
239
- return {
240
- backendId: this.capabilities.id,
241
- quoteId: normalizedQuoteId,
242
- payTo: normalizedPayTo,
243
- status: effectiveStatus,
244
- settlementCurrency: sessionCurrency ?? option?.settlementCurrency ?? contextCurrency ?? this.defaultCurrency.toUpperCase(),
245
- settlementAmount: sessionAmount ?? option?.settlementAmount ?? contextAmount,
246
- receivedAmount: paymentStatus === "paid" || paymentStatus === "no_payment_required" ? sessionAmount ?? option?.settlementAmount ?? contextAmount : void 0,
247
- amount: option?.amount ?? contextAmount,
248
- currency: option?.currency ?? contextCurrency,
249
- providerPaymentId: sessionId,
250
- transactionId: normalizeOptionalProviderString(
251
- readProviderString(session, "payment_intent")
252
- ),
253
- updatedAt: /* @__PURE__ */ new Date(),
254
- raw: session
255
- };
256
- }
257
- async sendPayout(input) {
258
- const currency = normalizeStripeCurrency(
259
- input.currency ?? this.defaultCurrency
260
- );
261
- this.assertSupportedCurrency(currency);
262
- const destination = normalizeNonEmptyString(
263
- input.destination,
264
- "Stripe payout destination"
265
- );
266
- const quoteId = input.quoteId === void 0 ? void 0 : normalizeNonEmptyString(input.quoteId, "Stripe payout quoteId");
267
- const amount = normalizePositivePaymentAmount(
268
- input.amount,
269
- currency,
270
- "Stripe payout amount"
271
- );
272
- const transfer = await this.stripeRequest(
273
- "/transfers",
274
- {
275
- method: "POST",
276
- idempotencyKey: input.idempotencyKey,
277
- body: formEncode({
278
- amount,
279
- currency,
280
- destination,
281
- description: input.memo,
282
- ...flattenStripeMetadata(input.metadata),
283
- ...quoteId === void 0 ? {} : { "metadata[quoteId]": quoteId }
284
- })
285
- }
286
- );
287
- return {
288
- backendId: this.capabilities.id,
289
- status: "submitted",
290
- payoutId: normalizeOptionalProviderString(
291
- readProviderString(transfer, "id")
292
- ),
293
- destination,
294
- amount,
295
- currency: currency.toUpperCase(),
296
- raw: transfer
297
- };
298
- }
299
- async refundPayment(input) {
300
- const rawPaymentReference = input.paymentId ?? input.transactionId;
301
- if (!rawPaymentReference) {
302
- throw new PaymentConfigurationError(
303
- "Stripe refunds require paymentId or transactionId."
304
- );
305
- }
306
- const paymentReference = normalizeNonEmptyString(
307
- rawPaymentReference,
308
- "Stripe refund payment reference"
309
- );
310
- const currency = normalizeStripeCurrency(
311
- input.currency ?? this.defaultCurrency
312
- );
313
- this.assertSupportedCurrency(currency);
314
- const amount = input.amount === void 0 ? void 0 : normalizePositivePaymentAmount(
315
- input.amount,
316
- currency,
317
- "Stripe refund amount"
318
- );
319
- const refundReferenceParam = await this.resolveStripeRefundReferenceParam(paymentReference);
320
- const refundParams = {
321
- ...refundReferenceParam,
322
- amount: amount === void 0 ? void 0 : amount,
323
- reason: input.reason,
324
- ...flattenStripeMetadata(input.metadata)
325
- };
326
- const refund = await this.stripeRequest(
327
- "/refunds",
328
- {
329
- method: "POST",
330
- idempotencyKey: input.idempotencyKey,
331
- body: formEncode(refundParams)
332
- }
333
- );
334
- return {
335
- backendId: this.capabilities.id,
336
- status: mapStripeRefundStatus(readString(refund, "status")),
337
- refundId: normalizeOptionalProviderString(
338
- readProviderString(refund, "id")
339
- ),
340
- transactionId: paymentReference,
341
- // Read the settled amount and currency back from the refund object
342
- // (mirrors capturePayment) so a full refund — no `input.amount` supplied,
343
- // and possibly a currency differing from the adapter default when
344
- // `input.currency` is omitted surfaces what Stripe actually refunded
345
- // instead of the request defaults.
346
- amount: readSafeInteger(refund, "amount") ?? amount,
347
- currency: readStripeResultCurrency(refund) ?? currency.toUpperCase(),
348
- raw: refund
349
- };
350
- }
351
- async authorizePayment(input) {
352
- const currency = normalizeStripeCurrency(input.currency);
353
- this.assertSupportedCurrency(currency);
354
- const amount = normalizePositivePaymentAmount(
355
- input.amount,
356
- currency,
357
- "Stripe authorize amount"
358
- );
359
- const paymentMethod = normalizeNonEmptyString(
360
- input.providerPaymentMethodId,
361
- "Stripe authorize providerPaymentMethodId"
362
- );
363
- const providerCustomerId = input.providerCustomerId === void 0 ? void 0 : normalizeNonEmptyString(
364
- input.providerCustomerId,
365
- "Stripe authorize providerCustomerId"
366
- );
367
- const quoteId = input.quoteId === void 0 ? void 0 : normalizeNonEmptyString(input.quoteId, "Stripe authorize quoteId");
368
- const intent = await this.stripeRequest(
369
- "/payment_intents",
370
- {
371
- method: "POST",
372
- idempotencyKey: input.idempotencyKey,
373
- body: formEncode({
374
- amount,
375
- currency,
376
- confirm: true,
377
- "payment_method_types[]": "card",
378
- description: input.description,
379
- ...Object.fromEntries([
380
- ["payment_method", paymentMethod],
381
- ["capture_method", "manual"],
382
- // Off-session (merchant-initiated) by default the primary use is
383
- // charging a saved card while the buyer is absent. Without this,
384
- // Stripe treats it as on-session and may raise an SCA challenge the
385
- // absent buyer can't complete, so the hold is never placed.
386
- ["off_session", input.offSession ?? true]
387
- ]),
388
- ...providerCustomerId === void 0 ? {} : { customer: providerCustomerId },
389
- ...flattenStripeMetadata(input.metadata),
390
- ...quoteId === void 0 ? {} : { "metadata[quoteId]": quoteId }
391
- })
392
- }
393
- );
394
- const providerPaymentId = normalizeOptionalProviderString(
395
- readProviderString(intent, "id")
396
- );
397
- if (!providerPaymentId) {
398
- throw new PaymentProviderError(
399
- "Stripe PaymentIntent response did not include an id."
400
- );
401
- }
402
- const status = mapStripeAuthorizationStatus(readString(intent, "status"));
403
- const actionRequired = status === "requires_action";
404
- return {
405
- backendId: this.capabilities.id,
406
- status,
407
- providerPaymentId,
408
- amount: readSafeInteger(intent, "amount") ?? amount,
409
- currency: (readString(intent, "currency") ?? currency).toUpperCase(),
410
- clientSecret: actionRequired ? normalizeOptionalProviderString(
411
- readProviderString(intent, "client_secret")
412
- ) : void 0,
413
- nextAction: actionRequired ? intent.next_action ?? void 0 : void 0,
414
- raw: intent
415
- };
416
- }
417
- async capturePayment(input) {
418
- const providerPaymentId = normalizeNonEmptyString(
419
- input.providerPaymentId,
420
- "Stripe capture providerPaymentId"
421
- );
422
- const amount = input.amount === void 0 ? void 0 : normalizePositiveMinorUnitAmount(
423
- input.amount,
424
- "Stripe capture amount"
425
- );
426
- const intent = await this.stripeRequest(
427
- `/payment_intents/${encodeURIComponent(providerPaymentId)}/capture`,
428
- {
429
- method: "POST",
430
- idempotencyKey: input.idempotencyKey,
431
- body: formEncode({
432
- ...Object.fromEntries([["amount_to_capture", amount]]),
433
- ...flattenStripeMetadata(input.metadata)
434
- })
435
- }
436
- );
437
- return {
438
- backendId: this.capabilities.id,
439
- status: mapStripeCaptureStatus(readString(intent, "status")),
440
- providerPaymentId: normalizeOptionalProviderString(readProviderString(intent, "id")) ?? providerPaymentId,
441
- amount: readSafeInteger(intent, "amount_received") ?? readSafeInteger(intent, "amount") ?? amount,
442
- currency: readStripeResultCurrency(intent),
443
- raw: intent
444
- };
445
- }
446
- async voidPayment(input) {
447
- const providerPaymentId = normalizeNonEmptyString(
448
- input.providerPaymentId,
449
- "Stripe void providerPaymentId"
450
- );
451
- const reason = input.reason === void 0 ? void 0 : normalizeNonEmptyString(input.reason, "Stripe void reason");
452
- const intent = await this.stripeRequest(
453
- `/payment_intents/${encodeURIComponent(providerPaymentId)}/cancel`,
454
- {
455
- method: "POST",
456
- idempotencyKey: input.idempotencyKey,
457
- body: formEncode(Object.fromEntries([["cancellation_reason", reason]]))
458
- }
459
- );
460
- return {
461
- backendId: this.capabilities.id,
462
- status: mapStripeVoidStatus(readString(intent, "status")),
463
- providerPaymentId: normalizeOptionalProviderString(readProviderString(intent, "id")) ?? providerPaymentId,
464
- amount: readSafeInteger(intent, "amount"),
465
- currency: readStripeResultCurrency(intent),
466
- raw: intent
467
- };
468
- }
469
- async createSetupSession(input) {
470
- const rawSuccessUrl = input.successUrl ?? this.options.successUrl;
471
- const rawCancelUrl = input.cancelUrl ?? this.options.cancelUrl;
472
- if (rawSuccessUrl === void 0 || rawCancelUrl === void 0) {
473
- throw new PaymentConfigurationError(
474
- "StripeAdapter createSetupSession requires successUrl and cancelUrl."
475
- );
476
- }
477
- const successUrl = normalizeUrlString(rawSuccessUrl, "Stripe successUrl");
478
- const cancelUrl = normalizeUrlString(rawCancelUrl, "Stripe cancelUrl");
479
- const currency = normalizeStripeCurrency(
480
- input.currency ?? this.defaultCurrency
481
- );
482
- this.assertSupportedCurrency(currency);
483
- const providerCustomerId = input.providerCustomerId === void 0 ? void 0 : normalizeNonEmptyString(
484
- input.providerCustomerId,
485
- "Stripe setup customer"
486
- );
487
- const customerParams = [];
488
- if (providerCustomerId !== void 0) {
489
- customerParams.push(["customer", providerCustomerId]);
490
- } else {
491
- customerParams.push(["customer_creation", "always"]);
492
- if (input.customerEmail !== void 0) {
493
- customerParams.push([
494
- "customer_email",
495
- normalizeNonEmptyString(
496
- input.customerEmail,
497
- "Stripe setup customerEmail"
498
- )
499
- ]);
500
- }
501
- }
502
- const params = {
503
- mode: "setup",
504
- currency,
505
- // Restrict to cards: SavedPaymentMethod exposes card display fields
506
- // (brand/last4/expiry), so allowing SEPA/Bacs/etc. would yield saved
507
- // methods with no usable display detail.
508
- "payment_method_types[]": "card",
509
- ...Object.fromEntries([
510
- ["success_url", successUrl],
511
- ["cancel_url", cancelUrl],
512
- ...customerParams
513
- ]),
514
- ...flattenStripeMetadata(input.metadata)
515
- };
516
- const session = await this.stripeRequest(
517
- "/checkout/sessions",
518
- {
519
- method: "POST",
520
- idempotencyKey: input.idempotencyKey,
521
- body: formEncode(params)
522
- }
523
- );
524
- const sessionId = normalizeOptionalProviderString(
525
- readProviderString(session, "id")
526
- );
527
- const url = normalizeOptionalProviderString(
528
- readProviderString(session, "url")
529
- );
530
- if (!sessionId || !url) {
531
- throw new PaymentProviderError(
532
- "Stripe setup Checkout Session response did not include id and url."
533
- );
534
- }
535
- return {
536
- backendId: this.capabilities.id,
537
- sessionId,
538
- url,
539
- providerCustomerId: providerCustomerId ?? normalizeOptionalProviderString(
540
- readProviderString(session, "customer")
541
- ),
542
- raw: session
543
- };
544
- }
545
- async getSetupResult(input) {
546
- const sessionId = normalizeNonEmptyString(
547
- input.sessionId,
548
- "Stripe setup sessionId"
549
- );
550
- const query = new URLSearchParams();
551
- query.append("expand[]", "setup_intent.payment_method");
552
- const session = await this.stripeRequest(
553
- `/checkout/sessions/${encodeURIComponent(sessionId)}?${query.toString()}`
554
- );
555
- const setupIntent = readObject(session, "setup_intent");
556
- const paymentMethod = readObject(setupIntent, "payment_method");
557
- const card = readObject(paymentMethod, "card");
558
- const providerCustomerId = normalizeOptionalProviderString(
559
- readProviderString(session, "customer") ?? readProviderString(readObject(session, "customer"), "id")
560
- );
561
- const providerPaymentMethodId = normalizeOptionalProviderString(
562
- readProviderString(paymentMethod, "id")
563
- );
564
- let status = mapStripeSetupStatus(
565
- readString(session, "status"),
566
- readString(setupIntent, "status")
567
- );
568
- if (status === "complete" && (!providerCustomerId || !providerPaymentMethodId)) {
569
- status = "pending";
570
- }
571
- return {
572
- backendId: this.capabilities.id,
573
- status,
574
- providerCustomerId,
575
- providerPaymentMethodId,
576
- type: readString(paymentMethod, "type"),
577
- brand: readString(card, "brand"),
578
- last4: readString(card, "last4"),
579
- expMonth: readSafeInteger(card, "exp_month"),
580
- expYear: readSafeInteger(card, "exp_year"),
581
- raw: session
582
- };
583
- }
584
- parseWebhookEvent(payload, signature) {
585
- if (!this.webhookSecret) {
586
- throw new PaymentConfigurationError(
587
- "StripeAdapter parseWebhookEvent requires webhookSecret."
588
- );
589
- }
590
- if (!signature) {
591
- throw new PaymentProviderError("Missing Stripe webhook signature.");
592
- }
593
- verifyStripeWebhookSignature(payload, signature, this.webhookSecret);
594
- const event = parseStripeWebhookPayload(payload);
595
- const id = readRequiredWebhookString(
596
- event,
597
- "id",
598
- "Stripe webhook event id"
599
- );
600
- const type = readRequiredWebhookString(
601
- event,
602
- "type",
603
- "Stripe webhook event type"
604
- );
605
- const duplicate = this.seenWebhookEventIds.has(id);
606
- if (!duplicate) {
607
- this.seenWebhookEventIds.add(id);
608
- while (this.seenWebhookEventIds.size > this.maxStoredWebhookEventIds) {
609
- const oldest = this.seenWebhookEventIds.values().next().value;
610
- if (oldest === void 0) {
611
- break;
612
- }
613
- this.seenWebhookEventIds.delete(oldest);
614
- }
615
- }
616
- const data = event.data?.object;
617
- return {
618
- id,
619
- type,
620
- status: mapStripeWebhookStatus(type, data),
621
- quoteId: normalizeOptionalWebhookString(
622
- // `client_reference_id` is a Checkout Session-only field; honor it only
623
- // for session events so a non-session object (a PaymentIntent / Charge /
624
- // Transfer, possibly from a malicious Connect account) can't spoof
625
- // correlation to another quote's id. Non-session events correlate only
626
- // via our own `metadata.quoteId`.
627
- (type.startsWith("checkout.session.") ? readString(data, "client_reference_id") : void 0) ?? readString(
628
- data?.metadata ?? {},
629
- "quoteId"
630
- )
631
- ),
632
- providerPaymentId: normalizeOptionalWebhookString(readString(data, "id")),
633
- duplicate,
634
- raw: event
635
- };
636
- }
637
- async stripeRequest(path, init = {}) {
638
- const headers = new Headers(init.headers);
639
- headers.set("Authorization", `Bearer ${this.secretKey}`);
640
- headers.set("Content-Type", "application/x-www-form-urlencoded");
641
- headers.set("Accept", "application/json");
642
- headers.set("Stripe-Version", this.apiVersion);
643
- if (init.idempotencyKey !== void 0) {
644
- headers.set(
645
- "Idempotency-Key",
646
- normalizeNonEmptyString(init.idempotencyKey, "Stripe idempotencyKey")
647
- );
648
- }
649
- const { idempotencyKey: _idempotencyKey, ...requestInit } = init;
650
- const response = await this.fetch(`${this.apiBaseUrl}${path}`, {
651
- ...requestInit,
652
- method: init.method ?? "GET",
653
- headers
654
- });
655
- return readJsonResponse(response, `Stripe ${path}`);
656
- }
657
- assertSupportedCurrency(currency) {
658
- if (!this.supportedCurrencies.has(currency)) {
659
- throw new PaymentConfigurationError(
660
- `StripeAdapter currency ${currency.toUpperCase()} is not configured as supported.`
661
- );
662
- }
663
- }
664
- async resolveStripeRefundReferenceParam(paymentReference) {
665
- if (paymentReference.startsWith("cs_")) {
666
- const session = await this.stripeRequest(
667
- `/checkout/sessions/${encodeURIComponent(paymentReference)}`
668
- );
669
- const paymentIntent = normalizeOptionalProviderString(
670
- readProviderString(session, "payment_intent")
671
- );
672
- if (!paymentIntent) {
673
- throw new PaymentProviderError(
674
- "Stripe Checkout Session did not include payment_intent for refund."
675
- );
676
- }
677
- return Object.fromEntries([["payment_intent", paymentIntent]]);
678
- }
679
- return stripeRefundReferenceParam(paymentReference);
680
- }
681
- }
4
+ //#region src/adapters/stripe.ts
5
+ var STRIPE_BACKEND_ID = "stripe";
6
+ var STRIPE_CHECKOUT_MIN_EXPIRY_MS = 1800 * 1e3;
7
+ var STRIPE_CHECKOUT_MAX_EXPIRY_MS = 1440 * 60 * 1e3;
8
+ var DEFAULT_STRIPE_API_VERSION = "2024-06-20";
9
+ var DEFAULT_MAX_STORED_WEBHOOK_EVENT_IDS = 5e4;
10
+ var StripeAdapter = class {
11
+ options;
12
+ capabilities;
13
+ fetch;
14
+ secretKey;
15
+ apiBaseUrl;
16
+ apiVersion;
17
+ defaultCurrency;
18
+ supportedCurrencies;
19
+ checkoutMode;
20
+ webhookSecret;
21
+ maxStoredPaymentOptions;
22
+ maxStoredWebhookEventIds;
23
+ optionsByQuote = /* @__PURE__ */ new Map();
24
+ seenWebhookEventIds = /* @__PURE__ */ new Set();
25
+ constructor(options) {
26
+ this.options = options;
27
+ if (typeof options.secretKey !== "string" || !options.secretKey.trim()) throw new PaymentConfigurationError("StripeAdapter requires secretKey.");
28
+ if (options.webhookSecret !== void 0) {
29
+ if (typeof options.webhookSecret !== "string") throw new PaymentConfigurationError("StripeAdapter webhookSecret must be a string when configured.");
30
+ if (!options.webhookSecret.trim()) throw new PaymentConfigurationError("StripeAdapter webhookSecret must not be empty when configured.");
31
+ }
32
+ if (options.apiBaseUrl !== void 0) {
33
+ if (typeof options.apiBaseUrl !== "string") throw new PaymentConfigurationError("StripeAdapter apiBaseUrl must be a string when configured.");
34
+ if (!options.apiBaseUrl.trim()) throw new PaymentConfigurationError("StripeAdapter apiBaseUrl must not be empty when configured.");
35
+ }
36
+ this.fetch = getFetch(options.fetch);
37
+ this.maxStoredPaymentOptions = normalizeMaxStoredPaymentOptions(options.maxStoredPaymentOptions, "StripeAdapter maxStoredPaymentOptions");
38
+ this.maxStoredWebhookEventIds = normalizeMaxStoredPaymentOptions(options.maxStoredWebhookEventIds ?? DEFAULT_MAX_STORED_WEBHOOK_EVENT_IDS, "StripeAdapter maxStoredWebhookEventIds");
39
+ this.secretKey = options.secretKey.trim();
40
+ this.apiBaseUrl = normalizeUrlString(options.apiBaseUrl ?? "https://api.stripe.com/v1", "StripeAdapter apiBaseUrl").replace(/\/$/, "");
41
+ this.apiVersion = normalizeNonEmptyString(options.apiVersion ?? DEFAULT_STRIPE_API_VERSION, "StripeAdapter apiVersion");
42
+ this.defaultCurrency = normalizeStripeCurrency(options.defaultCurrency ?? "usd");
43
+ this.supportedCurrencies = normalizeSupportedStripeCurrencies(options.supportedCurrencies, this.defaultCurrency);
44
+ this.checkoutMode = options.checkoutMode ?? "payment";
45
+ if (this.checkoutMode !== "payment") throw new PaymentConfigurationError("StripeAdapter only supports payment Checkout mode.");
46
+ this.webhookSecret = options.webhookSecret?.trim();
47
+ this.capabilities = {
48
+ id: STRIPE_BACKEND_ID,
49
+ displayName: "Stripe Checkout",
50
+ settlementCurrency: this.defaultCurrency.toUpperCase(),
51
+ supportedSettlementCurrencies: [...this.supportedCurrencies].map((currency) => currency.toUpperCase()),
52
+ chain: "stripe",
53
+ settlementShape: "url",
54
+ x402Capable: false,
55
+ confirmationLatency: {
56
+ expectedSeconds: 5,
57
+ maxExpectedSeconds: 600,
58
+ description: "Card payments usually confirm in seconds; bank debits can remain processing while Stripe settles them."
59
+ },
60
+ supportsRefunds: true,
61
+ supportsPayouts: true,
62
+ supportsWebhooks: true,
63
+ supportsManualCapture: true,
64
+ supportsSavedPaymentMethods: true,
65
+ metadata: { provider: "stripe" }
66
+ };
67
+ }
68
+ async createPaymentOption(input) {
69
+ const quoteId = normalizeNonEmptyString(input.quoteId, "Stripe quoteId");
70
+ const rawSuccessUrl = input.successUrl ?? this.options.successUrl;
71
+ const rawCancelUrl = input.cancelUrl ?? this.options.cancelUrl;
72
+ if (rawSuccessUrl === void 0 || rawCancelUrl === void 0) throw new PaymentConfigurationError("StripeAdapter createPaymentOption requires successUrl and cancelUrl.");
73
+ const successUrl = normalizeUrlString(rawSuccessUrl, "Stripe successUrl");
74
+ const cancelUrl = normalizeUrlString(rawCancelUrl, "Stripe cancelUrl");
75
+ const currency = normalizeStripeCurrency(input.currency);
76
+ this.assertSupportedCurrency(currency);
77
+ const amount = normalizePositivePaymentAmount(input.amount, currency, "Stripe Checkout amount");
78
+ const stripeExpiresAt = normalizeStripeCheckoutExpiresAt(normalizeDate(input.expiresAt));
79
+ const params = {
80
+ mode: this.checkoutMode,
81
+ ...Object.fromEntries([
82
+ ["success_url", successUrl],
83
+ ["cancel_url", cancelUrl],
84
+ ["client_reference_id", quoteId],
85
+ ["customer_email", input.buyerEmail],
86
+ ["expires_at", Math.floor(stripeExpiresAt.getTime() / 1e3)]
87
+ ]),
88
+ "line_items[0][quantity]": 1,
89
+ "line_items[0][price_data][currency]": currency,
90
+ "line_items[0][price_data][unit_amount]": amount,
91
+ "line_items[0][price_data][product_data][name]": input.description ?? `Quote ${quoteId}`,
92
+ ...flattenStripeMetadata(input.metadata),
93
+ "metadata[quoteId]": quoteId
94
+ };
95
+ const session = await this.stripeRequest("/checkout/sessions", {
96
+ method: "POST",
97
+ idempotencyKey: input.idempotencyKey,
98
+ body: formEncode(params)
99
+ });
100
+ const sessionId = normalizeOptionalProviderString(readProviderString(session, "id"));
101
+ const checkoutUrl = normalizeOptionalProviderString(readProviderString(session, "url"));
102
+ if (!sessionId || !checkoutUrl) throw new PaymentProviderError("Stripe Checkout Session response did not include id and url.");
103
+ const option = {
104
+ backendId: this.capabilities.id,
105
+ quoteId,
106
+ payTo: checkoutUrl,
107
+ settlementShape: "url",
108
+ settlementCurrency: currency.toUpperCase(),
109
+ settlementAmount: amount,
110
+ amount,
111
+ currency: currency.toUpperCase(),
112
+ expiresAt: stripeExpiresAt,
113
+ providerPaymentId: sessionId,
114
+ paymentUri: checkoutUrl,
115
+ metadata: {
116
+ sessionId,
117
+ paymentIntent: normalizeOptionalProviderString(readProviderString(session, "payment_intent"))
118
+ }
119
+ };
120
+ rememberPaymentOption(this.optionsByQuote, quoteId, option, this.maxStoredPaymentOptions);
121
+ return option;
122
+ }
123
+ watchPayment(input) {
124
+ return pollPaymentStatus({
125
+ ...input,
126
+ pollIntervalMs: input.pollIntervalMs ?? this.options.pollIntervalMs ?? 5e3
127
+ }, () => this.getStatus(input.quoteId, input.payTo, input.statusContext));
128
+ }
129
+ async getStatus(quoteId, payTo, context = {}) {
130
+ const normalizedQuoteId = normalizeNonEmptyString(quoteId, "Stripe quoteId");
131
+ const normalizedPayTo = normalizeNonEmptyString(payTo, "Stripe payTo");
132
+ const contextProviderPaymentId = context.providerPaymentId === void 0 ? void 0 : normalizeNonEmptyString(context.providerPaymentId, "Stripe providerPaymentId");
133
+ const sessionId = this.optionsByQuote.get(normalizedQuoteId)?.providerPaymentId ?? contextProviderPaymentId ?? extractStripeSessionId(normalizedPayTo);
134
+ if (!sessionId) throw new PaymentConfigurationError("Stripe getStatus requires a Checkout Session id or URL.");
135
+ const session = await this.stripeRequest(`/checkout/sessions/${encodeURIComponent(sessionId)}`);
136
+ const sessionQuoteId = readStripeSessionQuoteId(session);
137
+ if (sessionQuoteId !== void 0 && sessionQuoteId !== normalizedQuoteId) throw new PaymentVerificationError(`Stripe Checkout Session quoteId ${sessionQuoteId} did not match ${normalizedQuoteId}.`);
138
+ const paymentStatus = readString(session, "payment_status");
139
+ const status = mapStripeStatus(readString(session, "status"), paymentStatus);
140
+ const option = this.optionsByQuote.get(normalizedQuoteId);
141
+ const contextAmount = context.amount === void 0 ? void 0 : normalizeMinorUnitAmount(context.amount, "Stripe status");
142
+ const contextCurrency = context.currency === void 0 ? void 0 : normalizeStripeCurrency(context.currency).toUpperCase();
143
+ const sessionCurrency = readString(session, "currency") === void 0 ? void 0 : normalizeStripeCurrency(readString(session, "currency") ?? "").toUpperCase();
144
+ const sessionAmount = readSafeInteger(session, "amount_total");
145
+ const effectiveStatus = applyExpiryToPendingStatus(status, option?.expiresAt ?? (context.expiresAt === void 0 ? void 0 : normalizeDate(context.expiresAt)));
146
+ return {
147
+ backendId: this.capabilities.id,
148
+ quoteId: normalizedQuoteId,
149
+ payTo: normalizedPayTo,
150
+ status: effectiveStatus,
151
+ settlementCurrency: sessionCurrency ?? option?.settlementCurrency ?? contextCurrency ?? this.defaultCurrency.toUpperCase(),
152
+ settlementAmount: sessionAmount ?? option?.settlementAmount ?? contextAmount,
153
+ receivedAmount: paymentStatus === "paid" || paymentStatus === "no_payment_required" ? sessionAmount ?? option?.settlementAmount ?? contextAmount : void 0,
154
+ amount: option?.amount ?? contextAmount,
155
+ currency: option?.currency ?? contextCurrency,
156
+ providerPaymentId: sessionId,
157
+ transactionId: normalizeOptionalProviderString(readProviderString(session, "payment_intent")),
158
+ updatedAt: /* @__PURE__ */ new Date(),
159
+ raw: session
160
+ };
161
+ }
162
+ async sendPayout(input) {
163
+ const currency = normalizeStripeCurrency(input.currency ?? this.defaultCurrency);
164
+ this.assertSupportedCurrency(currency);
165
+ const destination = normalizeNonEmptyString(input.destination, "Stripe payout destination");
166
+ const quoteId = input.quoteId === void 0 ? void 0 : normalizeNonEmptyString(input.quoteId, "Stripe payout quoteId");
167
+ const amount = normalizePositivePaymentAmount(input.amount, currency, "Stripe payout amount");
168
+ const transfer = await this.stripeRequest("/transfers", {
169
+ method: "POST",
170
+ idempotencyKey: input.idempotencyKey,
171
+ body: formEncode({
172
+ amount,
173
+ currency,
174
+ destination,
175
+ description: input.memo,
176
+ ...flattenStripeMetadata(input.metadata),
177
+ ...quoteId === void 0 ? {} : { "metadata[quoteId]": quoteId }
178
+ })
179
+ });
180
+ return {
181
+ backendId: this.capabilities.id,
182
+ status: "submitted",
183
+ payoutId: normalizeOptionalProviderString(readProviderString(transfer, "id")),
184
+ destination,
185
+ amount,
186
+ currency: currency.toUpperCase(),
187
+ raw: transfer
188
+ };
189
+ }
190
+ async refundPayment(input) {
191
+ const rawPaymentReference = input.paymentId ?? input.transactionId;
192
+ if (!rawPaymentReference) throw new PaymentConfigurationError("Stripe refunds require paymentId or transactionId.");
193
+ const paymentReference = normalizeNonEmptyString(rawPaymentReference, "Stripe refund payment reference");
194
+ const currency = normalizeStripeCurrency(input.currency ?? this.defaultCurrency);
195
+ this.assertSupportedCurrency(currency);
196
+ const amount = input.amount === void 0 ? void 0 : normalizePositivePaymentAmount(input.amount, currency, "Stripe refund amount");
197
+ const refundParams = {
198
+ ...await this.resolveStripeRefundReferenceParam(paymentReference),
199
+ amount: amount === void 0 ? void 0 : amount,
200
+ reason: input.reason,
201
+ ...flattenStripeMetadata(input.metadata)
202
+ };
203
+ const refund = await this.stripeRequest("/refunds", {
204
+ method: "POST",
205
+ idempotencyKey: input.idempotencyKey,
206
+ body: formEncode(refundParams)
207
+ });
208
+ return {
209
+ backendId: this.capabilities.id,
210
+ status: mapStripeRefundStatus(readString(refund, "status")),
211
+ refundId: normalizeOptionalProviderString(readProviderString(refund, "id")),
212
+ transactionId: paymentReference,
213
+ amount: readSafeInteger(refund, "amount") ?? amount,
214
+ currency: readStripeResultCurrency(refund) ?? currency.toUpperCase(),
215
+ raw: refund
216
+ };
217
+ }
218
+ async authorizePayment(input) {
219
+ const currency = normalizeStripeCurrency(input.currency);
220
+ this.assertSupportedCurrency(currency);
221
+ const amount = normalizePositivePaymentAmount(input.amount, currency, "Stripe authorize amount");
222
+ const paymentMethod = normalizeNonEmptyString(input.providerPaymentMethodId, "Stripe authorize providerPaymentMethodId");
223
+ const providerCustomerId = input.providerCustomerId === void 0 ? void 0 : normalizeNonEmptyString(input.providerCustomerId, "Stripe authorize providerCustomerId");
224
+ const quoteId = input.quoteId === void 0 ? void 0 : normalizeNonEmptyString(input.quoteId, "Stripe authorize quoteId");
225
+ const intent = await this.stripeRequest("/payment_intents", {
226
+ method: "POST",
227
+ idempotencyKey: input.idempotencyKey,
228
+ body: formEncode({
229
+ amount,
230
+ currency,
231
+ confirm: true,
232
+ "payment_method_types[]": "card",
233
+ description: input.description,
234
+ ...Object.fromEntries([
235
+ ["payment_method", paymentMethod],
236
+ ["capture_method", "manual"],
237
+ ["off_session", input.offSession ?? true]
238
+ ]),
239
+ ...providerCustomerId === void 0 ? {} : { customer: providerCustomerId },
240
+ ...flattenStripeMetadata(input.metadata),
241
+ ...quoteId === void 0 ? {} : { "metadata[quoteId]": quoteId }
242
+ })
243
+ });
244
+ const providerPaymentId = normalizeOptionalProviderString(readProviderString(intent, "id"));
245
+ if (!providerPaymentId) throw new PaymentProviderError("Stripe PaymentIntent response did not include an id.");
246
+ const status = mapStripeAuthorizationStatus(readString(intent, "status"));
247
+ const actionRequired = status === "requires_action";
248
+ return {
249
+ backendId: this.capabilities.id,
250
+ status,
251
+ providerPaymentId,
252
+ amount: readSafeInteger(intent, "amount") ?? amount,
253
+ currency: (readString(intent, "currency") ?? currency).toUpperCase(),
254
+ clientSecret: actionRequired ? normalizeOptionalProviderString(readProviderString(intent, "client_secret")) : void 0,
255
+ nextAction: actionRequired ? intent.next_action ?? void 0 : void 0,
256
+ raw: intent
257
+ };
258
+ }
259
+ async capturePayment(input) {
260
+ const providerPaymentId = normalizeNonEmptyString(input.providerPaymentId, "Stripe capture providerPaymentId");
261
+ const amount = input.amount === void 0 ? void 0 : normalizePositiveMinorUnitAmount(input.amount, "Stripe capture amount");
262
+ const intent = await this.stripeRequest(`/payment_intents/${encodeURIComponent(providerPaymentId)}/capture`, {
263
+ method: "POST",
264
+ idempotencyKey: input.idempotencyKey,
265
+ body: formEncode({
266
+ ...Object.fromEntries([["amount_to_capture", amount]]),
267
+ ...flattenStripeMetadata(input.metadata)
268
+ })
269
+ });
270
+ return {
271
+ backendId: this.capabilities.id,
272
+ status: mapStripeCaptureStatus(readString(intent, "status")),
273
+ providerPaymentId: normalizeOptionalProviderString(readProviderString(intent, "id")) ?? providerPaymentId,
274
+ amount: readSafeInteger(intent, "amount_received") ?? readSafeInteger(intent, "amount") ?? amount,
275
+ currency: readStripeResultCurrency(intent),
276
+ raw: intent
277
+ };
278
+ }
279
+ async voidPayment(input) {
280
+ const providerPaymentId = normalizeNonEmptyString(input.providerPaymentId, "Stripe void providerPaymentId");
281
+ const reason = input.reason === void 0 ? void 0 : normalizeNonEmptyString(input.reason, "Stripe void reason");
282
+ const intent = await this.stripeRequest(`/payment_intents/${encodeURIComponent(providerPaymentId)}/cancel`, {
283
+ method: "POST",
284
+ idempotencyKey: input.idempotencyKey,
285
+ body: formEncode(Object.fromEntries([["cancellation_reason", reason]]))
286
+ });
287
+ return {
288
+ backendId: this.capabilities.id,
289
+ status: mapStripeVoidStatus(readString(intent, "status")),
290
+ providerPaymentId: normalizeOptionalProviderString(readProviderString(intent, "id")) ?? providerPaymentId,
291
+ amount: readSafeInteger(intent, "amount"),
292
+ currency: readStripeResultCurrency(intent),
293
+ raw: intent
294
+ };
295
+ }
296
+ async createSetupSession(input) {
297
+ const rawSuccessUrl = input.successUrl ?? this.options.successUrl;
298
+ const rawCancelUrl = input.cancelUrl ?? this.options.cancelUrl;
299
+ if (rawSuccessUrl === void 0 || rawCancelUrl === void 0) throw new PaymentConfigurationError("StripeAdapter createSetupSession requires successUrl and cancelUrl.");
300
+ const successUrl = normalizeUrlString(rawSuccessUrl, "Stripe successUrl");
301
+ const cancelUrl = normalizeUrlString(rawCancelUrl, "Stripe cancelUrl");
302
+ const currency = normalizeStripeCurrency(input.currency ?? this.defaultCurrency);
303
+ this.assertSupportedCurrency(currency);
304
+ const providerCustomerId = input.providerCustomerId === void 0 ? void 0 : normalizeNonEmptyString(input.providerCustomerId, "Stripe setup customer");
305
+ const customerParams = [];
306
+ if (providerCustomerId !== void 0) customerParams.push(["customer", providerCustomerId]);
307
+ else {
308
+ customerParams.push(["customer_creation", "always"]);
309
+ if (input.customerEmail !== void 0) customerParams.push(["customer_email", normalizeNonEmptyString(input.customerEmail, "Stripe setup customerEmail")]);
310
+ }
311
+ const params = {
312
+ mode: "setup",
313
+ currency,
314
+ "payment_method_types[]": "card",
315
+ ...Object.fromEntries([
316
+ ["success_url", successUrl],
317
+ ["cancel_url", cancelUrl],
318
+ ...customerParams
319
+ ]),
320
+ ...flattenStripeMetadata(input.metadata)
321
+ };
322
+ const session = await this.stripeRequest("/checkout/sessions", {
323
+ method: "POST",
324
+ idempotencyKey: input.idempotencyKey,
325
+ body: formEncode(params)
326
+ });
327
+ const sessionId = normalizeOptionalProviderString(readProviderString(session, "id"));
328
+ const url = normalizeOptionalProviderString(readProviderString(session, "url"));
329
+ if (!sessionId || !url) throw new PaymentProviderError("Stripe setup Checkout Session response did not include id and url.");
330
+ return {
331
+ backendId: this.capabilities.id,
332
+ sessionId,
333
+ url,
334
+ providerCustomerId: providerCustomerId ?? normalizeOptionalProviderString(readProviderString(session, "customer")),
335
+ raw: session
336
+ };
337
+ }
338
+ async getSetupResult(input) {
339
+ const sessionId = normalizeNonEmptyString(input.sessionId, "Stripe setup sessionId");
340
+ const query = new URLSearchParams();
341
+ query.append("expand[]", "setup_intent.payment_method");
342
+ const session = await this.stripeRequest(`/checkout/sessions/${encodeURIComponent(sessionId)}?${query.toString()}`);
343
+ const setupIntent = readObject(session, "setup_intent");
344
+ const paymentMethod = readObject(setupIntent, "payment_method");
345
+ const card = readObject(paymentMethod, "card");
346
+ const providerCustomerId = normalizeOptionalProviderString(readProviderString(session, "customer") ?? readProviderString(readObject(session, "customer"), "id"));
347
+ const providerPaymentMethodId = normalizeOptionalProviderString(readProviderString(paymentMethod, "id"));
348
+ let status = mapStripeSetupStatus(readString(session, "status"), readString(setupIntent, "status"));
349
+ if (status === "complete" && (!providerCustomerId || !providerPaymentMethodId)) status = "pending";
350
+ return {
351
+ backendId: this.capabilities.id,
352
+ status,
353
+ providerCustomerId,
354
+ providerPaymentMethodId,
355
+ type: readString(paymentMethod, "type"),
356
+ brand: readString(card, "brand"),
357
+ last4: readString(card, "last4"),
358
+ expMonth: readSafeInteger(card, "exp_month"),
359
+ expYear: readSafeInteger(card, "exp_year"),
360
+ raw: session
361
+ };
362
+ }
363
+ parseWebhookEvent(payload, signature) {
364
+ if (!this.webhookSecret) throw new PaymentConfigurationError("StripeAdapter parseWebhookEvent requires webhookSecret.");
365
+ if (!signature) throw new PaymentProviderError("Missing Stripe webhook signature.");
366
+ verifyStripeWebhookSignature(payload, signature, this.webhookSecret);
367
+ const event = parseStripeWebhookPayload(payload);
368
+ const id = readRequiredWebhookString(event, "id", "Stripe webhook event id");
369
+ const type = readRequiredWebhookString(event, "type", "Stripe webhook event type");
370
+ const duplicate = this.seenWebhookEventIds.has(id);
371
+ if (!duplicate) {
372
+ this.seenWebhookEventIds.add(id);
373
+ while (this.seenWebhookEventIds.size > this.maxStoredWebhookEventIds) {
374
+ const oldest = this.seenWebhookEventIds.values().next().value;
375
+ if (oldest === void 0) break;
376
+ this.seenWebhookEventIds.delete(oldest);
377
+ }
378
+ }
379
+ const data = event.data?.object;
380
+ return {
381
+ id,
382
+ type,
383
+ status: mapStripeWebhookStatus(type, data),
384
+ quoteId: normalizeOptionalWebhookString((type.startsWith("checkout.session.") ? readString(data, "client_reference_id") : void 0) ?? readString(data?.metadata ?? {}, "quoteId")),
385
+ providerPaymentId: normalizeOptionalWebhookString(readString(data, "id")),
386
+ duplicate,
387
+ raw: event
388
+ };
389
+ }
390
+ async stripeRequest(path, init = {}) {
391
+ const headers = new Headers(init.headers);
392
+ headers.set("Authorization", `Bearer ${this.secretKey}`);
393
+ headers.set("Content-Type", "application/x-www-form-urlencoded");
394
+ headers.set("Accept", "application/json");
395
+ headers.set("Stripe-Version", this.apiVersion);
396
+ if (init.idempotencyKey !== void 0) headers.set("Idempotency-Key", normalizeNonEmptyString(init.idempotencyKey, "Stripe idempotencyKey"));
397
+ const { idempotencyKey: _idempotencyKey, ...requestInit } = init;
398
+ return readJsonResponse(await this.fetch(`${this.apiBaseUrl}${path}`, {
399
+ ...requestInit,
400
+ method: init.method ?? "GET",
401
+ headers
402
+ }), `Stripe ${path}`);
403
+ }
404
+ assertSupportedCurrency(currency) {
405
+ if (!this.supportedCurrencies.has(currency)) throw new PaymentConfigurationError(`StripeAdapter currency ${currency.toUpperCase()} is not configured as supported.`);
406
+ }
407
+ async resolveStripeRefundReferenceParam(paymentReference) {
408
+ if (paymentReference.startsWith("cs_")) {
409
+ const paymentIntent = normalizeOptionalProviderString(readProviderString(await this.stripeRequest(`/checkout/sessions/${encodeURIComponent(paymentReference)}`), "payment_intent"));
410
+ if (!paymentIntent) throw new PaymentProviderError("Stripe Checkout Session did not include payment_intent for refund.");
411
+ return Object.fromEntries([["payment_intent", paymentIntent]]);
412
+ }
413
+ return stripeRefundReferenceParam(paymentReference);
414
+ }
415
+ };
682
416
  function stripeRefundReferenceParam(paymentReference) {
683
- if (paymentReference.startsWith("pi_")) {
684
- return Object.fromEntries([["payment_intent", paymentReference]]);
685
- }
686
- if (paymentReference.startsWith("ch_")) {
687
- return { charge: paymentReference };
688
- }
689
- throw new PaymentConfigurationError(
690
- "Stripe refunds require a Checkout Session (cs_), payment_intent (pi_), or charge (ch_) reference."
691
- );
417
+ if (paymentReference.startsWith("pi_")) return Object.fromEntries([["payment_intent", paymentReference]]);
418
+ if (paymentReference.startsWith("ch_")) return { charge: paymentReference };
419
+ throw new PaymentConfigurationError("Stripe refunds require a Checkout Session (cs_), payment_intent (pi_), or charge (ch_) reference.");
692
420
  }
693
421
  function normalizeStripeCurrency(value) {
694
- if (typeof value !== "string") {
695
- throw new PaymentConfigurationError(
696
- `StripeAdapter currency must be a three-letter ISO currency code, received ${String(value)}.`
697
- );
698
- }
699
- const normalized = value.trim().toLowerCase();
700
- if (!/^[a-z]{3}$/.test(normalized)) {
701
- throw new PaymentConfigurationError(
702
- `StripeAdapter currency must be a three-letter ISO currency code, received ${String(value)}.`
703
- );
704
- }
705
- return normalized;
422
+ if (typeof value !== "string") throw new PaymentConfigurationError(`StripeAdapter currency must be a three-letter ISO currency code, received ${String(value)}.`);
423
+ const normalized = value.trim().toLowerCase();
424
+ if (!/^[a-z]{3}$/.test(normalized)) throw new PaymentConfigurationError(`StripeAdapter currency must be a three-letter ISO currency code, received ${String(value)}.`);
425
+ return normalized;
706
426
  }
707
427
  function normalizeSupportedStripeCurrencies(configuredCurrencies, defaultCurrency) {
708
- const normalized = new Set(
709
- (configuredCurrencies ?? ["usd", "eur", "gbp", "cad"]).map(
710
- normalizeStripeCurrency
711
- )
712
- );
713
- normalized.add(defaultCurrency);
714
- return normalized;
428
+ const normalized = new Set((configuredCurrencies ?? [
429
+ "usd",
430
+ "eur",
431
+ "gbp",
432
+ "cad"
433
+ ]).map(normalizeStripeCurrency));
434
+ normalized.add(defaultCurrency);
435
+ return normalized;
715
436
  }
716
437
  function normalizeStripeCheckoutExpiresAt(expiresAt, now = Date.now()) {
717
- if (expiresAt.getTime() <= now) {
718
- throw new PaymentProviderError("Stripe Checkout expiry is in the past.");
719
- }
720
- const min = now + STRIPE_CHECKOUT_MIN_EXPIRY_MS;
721
- const max = now + STRIPE_CHECKOUT_MAX_EXPIRY_MS;
722
- if (expiresAt.getTime() < min || expiresAt.getTime() > max) {
723
- throw new PaymentConfigurationError(
724
- "Stripe Checkout expiry must be at least 30 minutes and no more than 24 hours from now."
725
- );
726
- }
727
- return expiresAt;
438
+ if (expiresAt.getTime() <= now) throw new PaymentProviderError("Stripe Checkout expiry is in the past.");
439
+ const min = now + STRIPE_CHECKOUT_MIN_EXPIRY_MS;
440
+ const max = now + STRIPE_CHECKOUT_MAX_EXPIRY_MS;
441
+ if (expiresAt.getTime() < min || expiresAt.getTime() > max) throw new PaymentConfigurationError("Stripe Checkout expiry must be at least 30 minutes and no more than 24 hours from now.");
442
+ return expiresAt;
728
443
  }
729
444
  function verifyStripeWebhookSignature(payload, signatureHeader, secret, toleranceSeconds = 300) {
730
- if (typeof payload !== "string") {
731
- throw new PaymentProviderError("Stripe webhook payload must be a string.");
732
- }
733
- const normalizedSignatureHeader = normalizeNonEmptyString(
734
- signatureHeader,
735
- "Stripe signature header"
736
- );
737
- const normalizedSecret = normalizeNonEmptyString(
738
- secret,
739
- "Stripe webhook secret"
740
- );
741
- if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) {
742
- throw new PaymentConfigurationError(
743
- `Stripe webhook toleranceSeconds must be a non-negative finite number, received ${String(toleranceSeconds)}.`
744
- );
745
- }
746
- const timestampCandidates = [];
747
- const signatures = [];
748
- for (const part of normalizedSignatureHeader.split(",")) {
749
- const [rawKey, ...rawValue] = part.split("=");
750
- const key = rawKey?.trim();
751
- const value = rawValue.join("=").trim();
752
- if (!key || !value) {
753
- continue;
754
- }
755
- if (key === "t") {
756
- timestampCandidates.push(value);
757
- }
758
- if (key === "v1") {
759
- signatures.push(value);
760
- }
761
- }
762
- if (timestampCandidates.length !== 1 || signatures.length === 0) {
763
- throw new PaymentProviderError("Invalid Stripe signature header.");
764
- }
765
- const timestamp = timestampCandidates[0] ?? "";
766
- if (!/^\d+$/.test(timestamp)) {
767
- throw new PaymentProviderError("Invalid Stripe signature header.");
768
- }
769
- const timestampSeconds = Number(timestamp);
770
- if (!Number.isSafeInteger(timestampSeconds)) {
771
- throw new PaymentProviderError("Invalid Stripe signature header.");
772
- }
773
- const nowSeconds = Date.now() / 1e3;
774
- const ageSeconds = nowSeconds - timestampSeconds;
775
- if (ageSeconds < -5) {
776
- throw new PaymentProviderError(
777
- "Stripe webhook signature timestamp is in the future."
778
- );
779
- }
780
- if (ageSeconds > toleranceSeconds) {
781
- throw new PaymentProviderError("Stripe webhook signature is too old.");
782
- }
783
- const expected = createHmac("sha256", normalizedSecret).update(`${timestamp}.${payload}`).digest("hex");
784
- const expectedBuffer = Buffer.from(expected, "hex");
785
- for (const signature of signatures) {
786
- if (!/^[0-9a-f]+$/i.test(signature)) {
787
- throw new PaymentProviderError("Invalid Stripe signature header.");
788
- }
789
- const actualBuffer = Buffer.from(signature, "hex");
790
- if (expectedBuffer.length === actualBuffer.length && timingSafeEqual(expectedBuffer, actualBuffer)) {
791
- return;
792
- }
793
- }
794
- throw new PaymentProviderError("Invalid Stripe webhook signature.");
445
+ if (typeof payload !== "string") throw new PaymentProviderError("Stripe webhook payload must be a string.");
446
+ const normalizedSignatureHeader = normalizeNonEmptyString(signatureHeader, "Stripe signature header");
447
+ const normalizedSecret = normalizeNonEmptyString(secret, "Stripe webhook secret");
448
+ if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) throw new PaymentConfigurationError(`Stripe webhook toleranceSeconds must be a non-negative finite number, received ${String(toleranceSeconds)}.`);
449
+ const timestampCandidates = [];
450
+ const signatures = [];
451
+ for (const part of normalizedSignatureHeader.split(",")) {
452
+ const [rawKey, ...rawValue] = part.split("=");
453
+ const key = rawKey?.trim();
454
+ const value = rawValue.join("=").trim();
455
+ if (!key || !value) continue;
456
+ if (key === "t") timestampCandidates.push(value);
457
+ if (key === "v1") signatures.push(value);
458
+ }
459
+ if (timestampCandidates.length !== 1 || signatures.length === 0) throw new PaymentProviderError("Invalid Stripe signature header.");
460
+ const timestamp = timestampCandidates[0] ?? "";
461
+ if (!/^\d+$/.test(timestamp)) throw new PaymentProviderError("Invalid Stripe signature header.");
462
+ const timestampSeconds = Number(timestamp);
463
+ if (!Number.isSafeInteger(timestampSeconds)) throw new PaymentProviderError("Invalid Stripe signature header.");
464
+ const ageSeconds = Date.now() / 1e3 - timestampSeconds;
465
+ if (ageSeconds < -5) throw new PaymentProviderError("Stripe webhook signature timestamp is in the future.");
466
+ if (ageSeconds > toleranceSeconds) throw new PaymentProviderError("Stripe webhook signature is too old.");
467
+ const expected = createHmac("sha256", normalizedSecret).update(`${timestamp}.${payload}`).digest("hex");
468
+ const expectedBuffer = Buffer.from(expected, "hex");
469
+ for (const signature of signatures) {
470
+ if (!/^[0-9a-f]+$/i.test(signature)) throw new PaymentProviderError("Invalid Stripe signature header.");
471
+ const actualBuffer = Buffer.from(signature, "hex");
472
+ if (expectedBuffer.length === actualBuffer.length && timingSafeEqual(expectedBuffer, actualBuffer)) return;
473
+ }
474
+ throw new PaymentProviderError("Invalid Stripe webhook signature.");
795
475
  }
796
476
  function parseStripeWebhookPayload(payload) {
797
- try {
798
- const event = JSON.parse(payload);
799
- if (!event || typeof event !== "object" || Array.isArray(event)) {
800
- throw new PaymentProviderError("Invalid Stripe webhook JSON payload.");
801
- }
802
- return event;
803
- } catch (error) {
804
- if (error instanceof PaymentProviderError) {
805
- throw error;
806
- }
807
- throw new PaymentProviderError("Invalid Stripe webhook JSON payload.", {
808
- cause: error
809
- });
810
- }
477
+ try {
478
+ const event = JSON.parse(payload);
479
+ if (!event || typeof event !== "object" || Array.isArray(event)) throw new PaymentProviderError("Invalid Stripe webhook JSON payload.");
480
+ return event;
481
+ } catch (error) {
482
+ if (error instanceof PaymentProviderError) throw error;
483
+ throw new PaymentProviderError("Invalid Stripe webhook JSON payload.", { cause: error });
484
+ }
811
485
  }
812
486
  function normalizeOptionalWebhookString(value) {
813
- const normalized = value?.trim();
814
- return normalized ? normalized : void 0;
487
+ const normalized = value?.trim();
488
+ return normalized ? normalized : void 0;
815
489
  }
816
490
  function normalizeOptionalProviderString(value) {
817
- const normalized = value?.trim();
818
- return normalized ? normalized : void 0;
491
+ const normalized = value?.trim();
492
+ return normalized ? normalized : void 0;
819
493
  }
820
494
  function readProviderString(value, key) {
821
- if (!value) {
822
- return void 0;
823
- }
824
- const item = value[key];
825
- return typeof item === "string" ? item : void 0;
495
+ if (!value) return;
496
+ const item = value[key];
497
+ return typeof item === "string" ? item : void 0;
826
498
  }
827
499
  function readRequiredWebhookString(value, key, context) {
828
- const item = value[key];
829
- if (typeof item !== "string") {
830
- throw new PaymentProviderError(`${context} must be a string.`);
831
- }
832
- return normalizeNonEmptyString(item, context);
500
+ const item = value[key];
501
+ if (typeof item !== "string") throw new PaymentProviderError(`${context} must be a string.`);
502
+ return normalizeNonEmptyString(item, context);
833
503
  }
834
504
  function readStripeSessionQuoteId(session) {
835
- return normalizeOptionalWebhookString(
836
- readString(session, "client_reference_id") ?? readString(
837
- session.metadata ?? {},
838
- "quoteId"
839
- )
840
- );
505
+ return normalizeOptionalWebhookString(readString(session, "client_reference_id") ?? readString(session.metadata ?? {}, "quoteId"));
841
506
  }
842
507
  function mapStripeStatus(checkoutStatus, paymentStatus) {
843
- if (paymentStatus === "paid" || paymentStatus === "no_payment_required") {
844
- return "confirmed";
845
- }
846
- if (checkoutStatus === "expired") {
847
- return "expired";
848
- }
849
- if (paymentStatus === "unpaid" || checkoutStatus === "open") {
850
- return "pending";
851
- }
852
- if (checkoutStatus === "complete") {
853
- return "processing";
854
- }
855
- return "pending";
508
+ if (paymentStatus === "paid" || paymentStatus === "no_payment_required") return "confirmed";
509
+ if (checkoutStatus === "expired") return "expired";
510
+ if (paymentStatus === "unpaid" || checkoutStatus === "open") return "pending";
511
+ if (checkoutStatus === "complete") return "processing";
512
+ return "pending";
856
513
  }
857
514
  function mapStripeWebhookStatus(type, object) {
858
- if (type === "checkout.session.completed") {
859
- return mapStripeStatus(
860
- readString(object, "status"),
861
- readString(object, "payment_status")
862
- );
863
- }
864
- if (type === "checkout.session.async_payment_succeeded") {
865
- return "confirmed";
866
- }
867
- if (type === "checkout.session.async_payment_failed") {
868
- return "failed";
869
- }
870
- if (type === "checkout.session.expired") {
871
- return "expired";
872
- }
873
- if (type === "payment_intent.succeeded" || type === "payment_intent.canceled" || type === "payment_intent.payment_failed") {
874
- const metadata = object?.metadata ?? {};
875
- const owned = normalizeOptionalWebhookString(readString(metadata, "quoteId")) !== void 0;
876
- if (owned) {
877
- if (type === "payment_intent.succeeded") {
878
- return "confirmed";
879
- }
880
- if (type === "payment_intent.canceled" && readString(object, "cancellation_reason") === "automatic") {
881
- return "expired";
882
- }
883
- return "failed";
884
- }
885
- return "processing";
886
- }
887
- const isPaymentInEvent = type?.startsWith("checkout.session.") === true || type?.startsWith("payment_intent.") === true || type?.startsWith("charge.") === true;
888
- if (isPaymentInEvent && type?.includes("refunded")) {
889
- return "refunded";
890
- }
891
- if (isPaymentInEvent && type?.includes("failed")) {
892
- return "failed";
893
- }
894
- return "processing";
515
+ if (type === "checkout.session.completed") return mapStripeStatus(readString(object, "status"), readString(object, "payment_status"));
516
+ if (type === "checkout.session.async_payment_succeeded") return "confirmed";
517
+ if (type === "checkout.session.async_payment_failed") return "failed";
518
+ if (type === "checkout.session.expired") return "expired";
519
+ if (type === "payment_intent.succeeded" || type === "payment_intent.canceled" || type === "payment_intent.payment_failed") {
520
+ if (normalizeOptionalWebhookString(readString(object?.metadata ?? {}, "quoteId")) !== void 0) {
521
+ if (type === "payment_intent.succeeded") return "confirmed";
522
+ if (type === "payment_intent.canceled" && readString(object, "cancellation_reason") === "automatic") return "expired";
523
+ return "failed";
524
+ }
525
+ return "processing";
526
+ }
527
+ const isPaymentInEvent = type?.startsWith("checkout.session.") === true || type?.startsWith("payment_intent.") === true || type?.startsWith("charge.") === true;
528
+ if (isPaymentInEvent && type?.includes("refunded")) return "refunded";
529
+ if (isPaymentInEvent && type?.includes("failed")) return "failed";
530
+ return "processing";
895
531
  }
896
532
  function mapStripeRefundStatus(status) {
897
- switch (status) {
898
- case "succeeded":
899
- return "succeeded";
900
- case "failed":
901
- case "canceled":
902
- return "failed";
903
- case "requires_action":
904
- return "requires_action";
905
- default:
906
- return "submitted";
907
- }
533
+ switch (status) {
534
+ case "succeeded": return "succeeded";
535
+ case "failed":
536
+ case "canceled": return "failed";
537
+ case "requires_action": return "requires_action";
538
+ default: return "submitted";
539
+ }
908
540
  }
909
541
  function mapStripeAuthorizationStatus(status) {
910
- switch (status) {
911
- case "requires_capture":
912
- return "requires_capture";
913
- case "succeeded":
914
- return "succeeded";
915
- case "requires_action":
916
- return "requires_action";
917
- case "processing":
918
- return "processing";
919
- default:
920
- return "failed";
921
- }
542
+ switch (status) {
543
+ case "requires_capture": return "requires_capture";
544
+ case "succeeded": return "succeeded";
545
+ case "requires_action": return "requires_action";
546
+ case "processing": return "processing";
547
+ default: return "failed";
548
+ }
922
549
  }
923
550
  function mapStripeCaptureStatus(status) {
924
- switch (status) {
925
- case "succeeded":
926
- return "succeeded";
927
- // Capturing an authorized intent shouldn't re-enter authentication; treat
928
- // any in-flight/action state as still processing rather than a failure.
929
- case "processing":
930
- case "requires_action":
931
- return "processing";
932
- default:
933
- return "failed";
934
- }
551
+ switch (status) {
552
+ case "succeeded": return "succeeded";
553
+ case "processing":
554
+ case "requires_action": return "processing";
555
+ default: return "failed";
556
+ }
935
557
  }
936
558
  function mapStripeVoidStatus(status) {
937
- return status === "canceled" ? "canceled" : "failed";
559
+ return status === "canceled" ? "canceled" : "failed";
938
560
  }
939
561
  function readStripeResultCurrency(value) {
940
- const currency = readString(value, "currency");
941
- return currency === void 0 ? void 0 : currency.toUpperCase();
562
+ const currency = readString(value, "currency");
563
+ return currency === void 0 ? void 0 : currency.toUpperCase();
942
564
  }
943
565
  function mapStripeSetupStatus(sessionStatus, setupIntentStatus) {
944
- if (sessionStatus === "expired") {
945
- return "expired";
946
- }
947
- if (sessionStatus === "complete") {
948
- if (setupIntentStatus === "succeeded") {
949
- return "complete";
950
- }
951
- if (setupIntentStatus === "canceled" || setupIntentStatus === "requires_payment_method") {
952
- return "failed";
953
- }
954
- }
955
- return "pending";
566
+ if (sessionStatus === "expired") return "expired";
567
+ if (sessionStatus === "complete") {
568
+ if (setupIntentStatus === "succeeded") return "complete";
569
+ if (setupIntentStatus === "canceled" || setupIntentStatus === "requires_payment_method") return "failed";
570
+ }
571
+ return "pending";
956
572
  }
957
573
  function readObject(value, key) {
958
- const item = value?.[key];
959
- return item && typeof item === "object" && !Array.isArray(item) ? item : void 0;
574
+ const item = value?.[key];
575
+ return item && typeof item === "object" && !Array.isArray(item) ? item : void 0;
960
576
  }
961
577
  function extractStripeSessionId(payTo) {
962
- if (/^cs_(test_|live_)?[A-Za-z0-9_]+$/.test(payTo)) {
963
- return payTo;
964
- }
965
- try {
966
- const url = new URL(payTo);
967
- const match = url.pathname.match(/\/pay\/([^/?#]+)/);
968
- const sessionId = match?.[1];
969
- return sessionId && /^cs_(test_|live_)?[A-Za-z0-9_]+$/.test(sessionId) ? sessionId : void 0;
970
- } catch {
971
- return void 0;
972
- }
578
+ if (/^cs_(test_|live_)?[A-Za-z0-9_]+$/.test(payTo)) return payTo;
579
+ try {
580
+ const sessionId = new URL(payTo).pathname.match(/\/pay\/([^/?#]+)/)?.[1];
581
+ return sessionId && /^cs_(test_|live_)?[A-Za-z0-9_]+$/.test(sessionId) ? sessionId : void 0;
582
+ } catch {
583
+ return;
584
+ }
973
585
  }
974
586
  function flattenStripeMetadata(metadata) {
975
- const result = {};
976
- for (const [key, value] of Object.entries(metadata ?? {})) {
977
- if (value !== void 0 && value !== null) {
978
- result[`metadata[${key}]`] = value;
979
- }
980
- }
981
- return result;
587
+ const result = {};
588
+ for (const [key, value] of Object.entries(metadata ?? {})) if (value !== void 0 && value !== null) result[`metadata[${key}]`] = value;
589
+ return result;
982
590
  }
983
591
  function readString(value, key) {
984
- if (!value) {
985
- return void 0;
986
- }
987
- const item = value[key];
988
- if (typeof item === "string") {
989
- return item;
990
- }
991
- if (typeof item === "number") {
992
- return String(item);
993
- }
994
- return void 0;
592
+ if (!value) return;
593
+ const item = value[key];
594
+ if (typeof item === "string") return item;
595
+ if (typeof item === "number") return String(item);
995
596
  }
996
597
  function readSafeInteger(value, key) {
997
- if (!value) {
998
- return void 0;
999
- }
1000
- const item = value[key];
1001
- return typeof item === "number" && Number.isSafeInteger(item) && item >= 0 ? item : void 0;
598
+ if (!value) return;
599
+ const item = value[key];
600
+ return typeof item === "number" && Number.isSafeInteger(item) && item >= 0 ? item : void 0;
1002
601
  }
1003
- export {
1004
- STRIPE_BACKEND_ID,
1005
- StripeAdapter,
1006
- verifyStripeWebhookSignature
1007
- };
1008
- //# sourceMappingURL=stripe.js.map
602
+ //#endregion
603
+ export { STRIPE_BACKEND_ID, StripeAdapter, verifyStripeWebhookSignature };
604
+
605
+ //# sourceMappingURL=stripe.js.map