@happyvertical/payments 0.80.0 → 0.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/base-usdc.js +720 -1101
- package/dist/adapters/base-usdc.js.map +1 -1
- package/dist/adapters/btc.js +465 -790
- package/dist/adapters/btc.js.map +1 -1
- package/dist/adapters/stripe.js +556 -959
- package/dist/adapters/stripe.js.map +1 -1
- package/dist/chunks/errors-feZrwnTy.js +39 -0
- package/dist/chunks/errors-feZrwnTy.js.map +1 -0
- package/dist/chunks/shared-DVl6UzD9.js +260 -0
- package/dist/chunks/shared-DVl6UzD9.js.map +1 -0
- package/dist/index.js +33 -47
- package/dist/index.js.map +1 -1
- package/dist/testing/conformance.js +93 -109
- package/dist/testing/conformance.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunks/errors-BgFC46qQ.js +0 -45
- package/dist/chunks/errors-BgFC46qQ.js.map +0 -1
- package/dist/chunks/shared-DGHSqDQT.js +0 -392
- package/dist/chunks/shared-DGHSqDQT.js.map +0 -1
package/dist/adapters/stripe.js
CHANGED
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
684
|
-
|
|
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
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
814
|
-
|
|
487
|
+
const normalized = value?.trim();
|
|
488
|
+
return normalized ? normalized : void 0;
|
|
815
489
|
}
|
|
816
490
|
function normalizeOptionalProviderString(value) {
|
|
817
|
-
|
|
818
|
-
|
|
491
|
+
const normalized = value?.trim();
|
|
492
|
+
return normalized ? normalized : void 0;
|
|
819
493
|
}
|
|
820
494
|
function readProviderString(value, key) {
|
|
821
|
-
|
|
822
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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
|
-
|
|
559
|
+
return status === "canceled" ? "canceled" : "failed";
|
|
938
560
|
}
|
|
939
561
|
function readStripeResultCurrency(value) {
|
|
940
|
-
|
|
941
|
-
|
|
562
|
+
const currency = readString(value, "currency");
|
|
563
|
+
return currency === void 0 ? void 0 : currency.toUpperCase();
|
|
942
564
|
}
|
|
943
565
|
function mapStripeSetupStatus(sessionStatus, setupIntentStatus) {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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
|
-
|
|
959
|
-
|
|
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
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
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
|
-
|
|
998
|
-
|
|
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
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
};
|
|
1008
|
-
//# sourceMappingURL=stripe.js.map
|
|
602
|
+
//#endregion
|
|
603
|
+
export { STRIPE_BACKEND_ID, StripeAdapter, verifyStripeWebhookSignature };
|
|
604
|
+
|
|
605
|
+
//# sourceMappingURL=stripe.js.map
|