@flopay/js 1.4.0 → 1.4.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/README.md CHANGED
@@ -24,7 +24,13 @@ const flopay = await loadFloPay('pk_test_...', {
24
24
  });
25
25
  ```
26
26
 
27
- `loadFloPay` caches the instance -- calling it again with the same key returns the cached `FloPay` instance. The optional second argument accepts a `FloPayConfig` with `billingApiUrl`, `locale`, `appearance`, and `telemetry`; set `telemetry: false` to opt out of privacy-safe operational telemetry.
27
+ `loadFloPay` caches by publishable key and behavior-affecting options. Concurrent
28
+ calls with the same key and options share one in-flight provider initialization
29
+ and resolve to the same `FloPay` instance. A failed initialization is evicted so
30
+ a later call can retry. The optional second argument accepts
31
+ `Omit<FloPayConfig, 'publishableKey'>`, including `billingApiUrl`, `locale`,
32
+ `appearance`, and `telemetry`; set `telemetry: false` to opt out of privacy-safe
33
+ operational telemetry.
28
34
 
29
35
  ### Create and Mount Elements
30
36
 
@@ -267,7 +273,7 @@ The same default-on behavior and boolean opt-out apply to direct `FloPay`,
267
273
 
268
274
  | Export | Description |
269
275
  |--------|-------------|
270
- | `loadFloPay(publishableKey, options?)` | Initializes the SDK. Returns a `Promise<FloPay>`. Caches by key. |
276
+ | `loadFloPay(publishableKey, options?)` | Initializes the SDK. Returns a `Promise<FloPay>`. Shares successful and in-flight work by key; failed work remains retryable. |
271
277
  | `FloPay` | Main SDK class. Methods: `elements()`, `submitElements()`, `createPaymentMethod()`, `confirmCardPayment()`, `cardCapture()`, `confirmPayment()`, `confirmPayPalPayment()`, `resumePayPalPayment()`, `retrieveSession()`, `retrieveUnifiedSession()`, `getRawProvider()`, `destroy()` |
272
278
  | `FloPayElements` | Element group manager. Methods: `create(type, options?)`, `getElement(type)`, `submit()`, `destroy()` |
273
279
  | `StripeAdapter` | `PaymentProviderAdapter` implementation for Stripe |
package/dist/index.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
@@ -48,7 +38,14 @@ module.exports = __toCommonJS(index_exports);
48
38
  var import_shared7 = require("@flopay/shared");
49
39
 
50
40
  // src/stripe-adapter.ts
41
+ var import_stripe_js = require("@stripe/stripe-js");
51
42
  var import_shared = require("@flopay/shared");
43
+
44
+ // src/card-three-ds.ts
45
+ var CARD_THREE_DS_ATTEMPT_TTL_MS = 15 * 6e4;
46
+ var MAX_CARD_THREE_DS_ATTEMPTS = 32;
47
+
48
+ // src/stripe-adapter.ts
52
49
  function toStripeElementType(type) {
53
50
  const map = {
54
51
  payment: "payment",
@@ -71,8 +68,6 @@ function toStripeAppearanceTheme(theme) {
71
68
  return "stripe";
72
69
  }
73
70
  }
74
- var CARD_THREE_DS_ATTEMPT_TTL_MS = 15 * 6e4;
75
- var MAX_CARD_THREE_DS_ATTEMPTS = 32;
76
71
  function cardThreeDsNow() {
77
72
  return globalThis.performance?.now() ?? Date.now();
78
73
  }
@@ -99,6 +94,24 @@ function wrapStripeElement(stripeElement) {
99
94
  }
100
95
  };
101
96
  }
97
+ function toStripeBillingDetails(billing) {
98
+ return {
99
+ billing_details: {
100
+ ...billing.email ? { email: billing.email } : {},
101
+ ...billing.name ? { name: billing.name } : {},
102
+ ...billing.address ? {
103
+ address: {
104
+ ...billing.address.country ? { country: billing.address.country } : {},
105
+ ...billing.address.postal_code ? { postal_code: billing.address.postal_code } : {},
106
+ ...billing.address.city ? { city: billing.address.city } : {},
107
+ ...billing.address.line1 ? { line1: billing.address.line1 } : {},
108
+ ...billing.address.line2 ? { line2: billing.address.line2 } : {},
109
+ ...billing.address.state ? { state: billing.address.state } : {}
110
+ }
111
+ } : {}
112
+ }
113
+ };
114
+ }
102
115
  var StripeAdapter = class {
103
116
  constructor() {
104
117
  this.name = "stripe";
@@ -116,17 +129,7 @@ var StripeAdapter = class {
116
129
  if (typeof window === "undefined") {
117
130
  return;
118
131
  }
119
- let loadStripe;
120
- try {
121
- ({ loadStripe } = await import("@stripe/stripe-js"));
122
- } catch {
123
- throw new import_shared.FloPayError(
124
- "Failed to load @stripe/stripe-js. Reinstall @flopay/js.",
125
- "api_error",
126
- { code: "StripeJsLoadFailed" }
127
- );
128
- }
129
- const stripe = await loadStripe(config.publishableKey, {
132
+ const stripe = await (0, import_stripe_js.loadStripe)(config.publishableKey, {
130
133
  locale: config.locale ?? "auto"
131
134
  });
132
135
  if (!stripe) {
@@ -237,22 +240,7 @@ var StripeAdapter = class {
237
240
  };
238
241
  }
239
242
  const cardNumberEl = this.elements.getElement("cardNumber");
240
- const stripeBilling = billingDetails ? {
241
- billing_details: {
242
- ...billingDetails.email ? { email: billingDetails.email } : {},
243
- ...billingDetails.name ? { name: billingDetails.name } : {},
244
- ...billingDetails.address ? {
245
- address: {
246
- ...billingDetails.address.country ? { country: billingDetails.address.country } : {},
247
- ...billingDetails.address.postal_code ? { postal_code: billingDetails.address.postal_code } : {},
248
- ...billingDetails.address.city ? { city: billingDetails.address.city } : {},
249
- ...billingDetails.address.line1 ? { line1: billingDetails.address.line1 } : {},
250
- ...billingDetails.address.line2 ? { line2: billingDetails.address.line2 } : {},
251
- ...billingDetails.address.state ? { state: billingDetails.address.state } : {}
252
- }
253
- } : {}
254
- }
255
- } : {};
243
+ const stripeBilling = billingDetails ? toStripeBillingDetails(billingDetails) : {};
256
244
  const { error, paymentMethod } = cardNumberEl ? await this.stripe.createPaymentMethod({
257
245
  type: "card",
258
246
  card: cardNumberEl,
@@ -372,22 +360,7 @@ var StripeAdapter = class {
372
360
  );
373
361
  }
374
362
  const billing = params.billingDetails;
375
- const paymentMethodData = billing ? {
376
- billing_details: {
377
- ...billing.email ? { email: billing.email } : {},
378
- ...billing.name ? { name: billing.name } : {},
379
- ...billing.address ? {
380
- address: {
381
- ...billing.address.country ? { country: billing.address.country } : {},
382
- ...billing.address.postal_code ? { postal_code: billing.address.postal_code } : {},
383
- ...billing.address.city ? { city: billing.address.city } : {},
384
- ...billing.address.line1 ? { line1: billing.address.line1 } : {},
385
- ...billing.address.line2 ? { line2: billing.address.line2 } : {},
386
- ...billing.address.state ? { state: billing.address.state } : {}
387
- }
388
- } : {}
389
- }
390
- } : void 0;
363
+ const paymentMethodData = billing ? toStripeBillingDetails(billing) : void 0;
391
364
  const { error, paymentIntent } = await this.stripe.confirmPayment({
392
365
  elements: this.elements,
393
366
  clientSecret: params.clientSecret,
@@ -612,6 +585,19 @@ var FloPayElements = class {
612
585
  // src/payment-api.ts
613
586
  var import_shared4 = require("@flopay/shared");
614
587
 
588
+ // src/api-error.ts
589
+ function readErrorString(value) {
590
+ return typeof value === "string" && value.trim() ? value : void 0;
591
+ }
592
+ function readErrorMessage(value) {
593
+ if (typeof value === "string") return value.trim() ? value : void 0;
594
+ if (Array.isArray(value)) {
595
+ const joined = value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).join("; ");
596
+ return joined || void 0;
597
+ }
598
+ return void 0;
599
+ }
600
+
615
601
  // src/session-display-cache.ts
616
602
  var STORAGE_KEY_PREFIX = "flopay_session_display:";
617
603
  var DEFAULT_TTL_MS = 60 * 60 * 1e3;
@@ -1035,17 +1021,10 @@ function telemetryFailure(error, fallbackCode) {
1035
1021
  };
1036
1022
  }
1037
1023
  function readString(payload, key) {
1038
- const value = payload?.[key];
1039
- return typeof value === "string" && value.trim() ? value : void 0;
1024
+ return readErrorString(payload?.[key]);
1040
1025
  }
1041
1026
  function readMessage(payload, key) {
1042
- const value = payload?.[key];
1043
- if (typeof value === "string") return value.trim() ? value : void 0;
1044
- if (Array.isArray(value)) {
1045
- const joined = value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).join("; ");
1046
- return joined || void 0;
1047
- }
1048
- return void 0;
1027
+ return readErrorMessage(payload?.[key]);
1049
1028
  }
1050
1029
  function readNumber(payload, key) {
1051
1030
  const value = payload?.[key];
@@ -2877,8 +2856,6 @@ function telemetryProvider(name) {
2877
2856
  if (name === "stripe" || name === "paypal" || name === "pcivault") return name;
2878
2857
  return "other";
2879
2858
  }
2880
- var CARD_THREE_DS_ATTEMPT_TTL_MS2 = 15 * 6e4;
2881
- var MAX_CARD_THREE_DS_ATTEMPTS2 = 32;
2882
2859
  var FloPay = class {
2883
2860
  constructor(provider, config, telemetryReporter) {
2884
2861
  this.currentElements = null;
@@ -3060,7 +3037,7 @@ var FloPay = class {
3060
3037
  trackCardThreeDsAttempt(attemptId, startedAt) {
3061
3038
  this.pruneExpiredCardThreeDsAttempts(startedAt);
3062
3039
  if (this.cardThreeDsStartedAt.has(attemptId)) return false;
3063
- while (this.cardThreeDsStartedAt.size >= MAX_CARD_THREE_DS_ATTEMPTS2) {
3040
+ while (this.cardThreeDsStartedAt.size >= MAX_CARD_THREE_DS_ATTEMPTS) {
3064
3041
  const oldestAttemptId = this.cardThreeDsStartedAt.keys().next().value;
3065
3042
  if (oldestAttemptId === void 0) break;
3066
3043
  this.cardThreeDsStartedAt.delete(oldestAttemptId);
@@ -3076,7 +3053,7 @@ var FloPay = class {
3076
3053
  }
3077
3054
  pruneExpiredCardThreeDsAttempts(now) {
3078
3055
  for (const [attemptId, startedAt] of this.cardThreeDsStartedAt) {
3079
- if (now - startedAt >= CARD_THREE_DS_ATTEMPT_TTL_MS2) {
3056
+ if (now - startedAt >= CARD_THREE_DS_ATTEMPT_TTL_MS) {
3080
3057
  this.cardThreeDsStartedAt.delete(attemptId);
3081
3058
  }
3082
3059
  }
@@ -3516,20 +3493,21 @@ function instanceCacheKey(publishableKey, options) {
3516
3493
  stableCacheValue(options?.appearance ?? null)
3517
3494
  ]);
3518
3495
  }
3496
+ var initializationCache = /* @__PURE__ */ new Map();
3519
3497
  async function loadFloPay(publishableKey, options) {
3520
3498
  if (!publishableKey) {
3521
- const reporter2 = new TelemetryReporter({
3499
+ const reporter = new TelemetryReporter({
3522
3500
  billingApiUrl: (0, import_shared7.resolveBillingApiUrl)(options?.billingApiUrl),
3523
3501
  sdkVersion: import_shared7.SDK_VERSION,
3524
3502
  enabled: options?.telemetry !== false
3525
3503
  });
3526
- reporter2.error({
3504
+ reporter.error({
3527
3505
  errorCode: "CONFIGURATION_INVALID",
3528
3506
  stage: "sdk_initialize",
3529
3507
  paymentMethodCategory: "unknown"
3530
3508
  });
3531
- void reporter2.flush().catch(() => {
3532
- }).finally(() => reporter2.destroy());
3509
+ void reporter.flush().catch(() => {
3510
+ }).finally(() => reporter.destroy());
3533
3511
  throw new import_shared7.FloPayError(
3534
3512
  "A publishable key is required to initialize FloPay.",
3535
3513
  "validation_error",
@@ -3547,71 +3525,80 @@ async function loadFloPay(publishableKey, options) {
3547
3525
  }
3548
3526
  return cached;
3549
3527
  }
3528
+ const initializing = initializationCache.get(cacheKey);
3529
+ if (initializing) return initializing;
3550
3530
  const config = {
3551
- publishableKey,
3552
- ...options
3531
+ ...options,
3532
+ publishableKey
3553
3533
  };
3554
- const reporter = new TelemetryReporter({
3555
- billingApiUrl: (0, import_shared7.resolveBillingApiUrl)(config.billingApiUrl),
3556
- sdkVersion: import_shared7.SDK_VERSION,
3557
- enabled: config.telemetry !== false
3558
- });
3559
- const initializationStarted = reporter.now();
3560
- reporter.log({ name: "sdk.initialize.started", stage: "sdk_initialize" });
3561
- reporter.log({ name: "sdk.cache.miss", stage: "sdk_initialize" });
3562
- reporter.log({
3563
- name: "provider.load.started",
3564
- stage: "provider_load",
3565
- provider: "stripe"
3566
- });
3567
- const adapter = new StripeAdapter();
3568
- try {
3569
- await adapter.initialize(config);
3570
- } catch (error) {
3571
- reporter.error({
3572
- errorCode: "SDK_INITIALIZATION_FAILED",
3534
+ const initialization = (async () => {
3535
+ const reporter = new TelemetryReporter({
3536
+ billingApiUrl: (0, import_shared7.resolveBillingApiUrl)(config.billingApiUrl),
3537
+ sdkVersion: import_shared7.SDK_VERSION,
3538
+ enabled: config.telemetry !== false
3539
+ });
3540
+ const initializationStarted = reporter.now();
3541
+ reporter.log({ name: "sdk.initialize.started", stage: "sdk_initialize" });
3542
+ reporter.log({ name: "sdk.cache.miss", stage: "sdk_initialize" });
3543
+ reporter.log({
3544
+ name: "provider.load.started",
3545
+ stage: "provider_load",
3546
+ provider: "stripe"
3547
+ });
3548
+ const adapter = new StripeAdapter();
3549
+ try {
3550
+ await adapter.initialize(config);
3551
+ } catch (error) {
3552
+ reporter.error({
3553
+ errorCode: "SDK_INITIALIZATION_FAILED",
3554
+ stage: "sdk_initialize",
3555
+ provider: "stripe",
3556
+ paymentMethodCategory: "unknown"
3557
+ });
3558
+ reporter.destroy();
3559
+ throw error;
3560
+ }
3561
+ reporter.log({ name: "provider.ready", stage: "provider_ready", provider: "stripe" });
3562
+ reporter.log({
3563
+ name: "provider.availability.checked",
3564
+ stage: "provider_ready",
3565
+ provider: "stripe"
3566
+ });
3567
+ reporter.log({ name: "sdk.initialize.ready", stage: "sdk_initialize" });
3568
+ const initializationDuration = reporter.now() - initializationStarted;
3569
+ reporter.performance({
3573
3570
  stage: "sdk_initialize",
3574
- provider: "stripe",
3575
- paymentMethodCategory: "unknown"
3571
+ durationMs: initializationDuration,
3572
+ durationMode: "machine",
3573
+ provider: "stripe"
3576
3574
  });
3577
- reporter.destroy();
3578
- throw error;
3575
+ reporter.performance({
3576
+ stage: "provider_ready",
3577
+ durationMs: initializationDuration,
3578
+ durationMode: "machine",
3579
+ provider: "stripe"
3580
+ });
3581
+ const instance = createInstrumentedFloPay(adapter, config, reporter);
3582
+ instanceCache.set(cacheKey, instance);
3583
+ return instance;
3584
+ })();
3585
+ initializationCache.set(cacheKey, initialization);
3586
+ try {
3587
+ return await initialization;
3588
+ } finally {
3589
+ if (initializationCache.get(cacheKey) === initialization) {
3590
+ initializationCache.delete(cacheKey);
3591
+ }
3579
3592
  }
3580
- reporter.log({ name: "provider.ready", stage: "provider_ready", provider: "stripe" });
3581
- reporter.log({
3582
- name: "provider.availability.checked",
3583
- stage: "provider_ready",
3584
- provider: "stripe"
3585
- });
3586
- reporter.log({ name: "sdk.initialize.ready", stage: "sdk_initialize" });
3587
- const initializationDuration = reporter.now() - initializationStarted;
3588
- reporter.performance({
3589
- stage: "sdk_initialize",
3590
- durationMs: initializationDuration,
3591
- durationMode: "machine",
3592
- provider: "stripe"
3593
- });
3594
- reporter.performance({
3595
- stage: "provider_ready",
3596
- durationMs: initializationDuration,
3597
- durationMode: "machine",
3598
- provider: "stripe"
3599
- });
3600
- const instance = createInstrumentedFloPay(adapter, config, reporter);
3601
- instanceCache.set(cacheKey, instance);
3602
- return instance;
3603
3593
  }
3604
3594
 
3605
3595
  // src/create-checkout-session.ts
3606
3596
  var import_shared8 = require("@flopay/shared");
3607
3597
  var MAX_COUPON_CODES = 5;
3608
- function readString2(value) {
3609
- return typeof value === "string" && value.trim() ? value : void 0;
3610
- }
3611
3598
  function buildCheckoutSessionError(status, payload) {
3612
3599
  const nested = payload?.error;
3613
- const code = readString2(payload?.code) ?? readString2(nested?.code) ?? `http_${status}`;
3614
- const message = readString2(payload?.message) ?? readString2(nested?.message) ?? defaultMessageForCode(code, status);
3600
+ const code = readErrorString(payload?.code) ?? readErrorString(nested?.code) ?? `http_${status}`;
3601
+ const message = readErrorString(payload?.message) ?? readErrorString(nested?.message) ?? defaultMessageForCode(code, status);
3615
3602
  return new import_shared8.FloPayError(message, "api_error", { code, statusCode: status });
3616
3603
  }
3617
3604
  function defaultMessageForCode(code, status) {