@flopay/shared 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,7 +77,7 @@ throw validationError('Email is required', 'email');
77
77
 
78
78
  | Type | Description |
79
79
  |------|-------------|
80
- | `FloPayConfig` | Top-level config: `publishableKey`, `locale?`, `appearance?`, `apiVersion?` |
80
+ | `FloPayConfig` | Top-level config: `publishableKey`, `locale?`, `appearance?`, `apiVersion?`, `telemetry?` (set `false` to opt out of browser operational telemetry) |
81
81
  | `FloPayAppearance` | Theme config: `theme` (`'default'`/`'flat'`/`'night'`/`'none'`), `variables?`, `rules?` |
82
82
  | `FloPayThemeVariables` | CSS custom property overrides: `colorPrimary`, `colorBackground`, `colorText`, `colorDanger`, `borderRadius`, `fontFamily`, `fontSizeBase`, `spacingUnit` |
83
83
  | `ButtonsLayoutStyles` | Style overrides for the `buttons` layout: `cardButton`, `cardButtonFontSize`, `cardFormContainer`, `cardInputBorder`, `cardInputColor`, `cardInputPlaceholderColor`, `cardInputFontSize`, `cardInputBackground`, `nameInput`, `backButton`, `backButtonFontSize`, `backButtonIcon`, `submitButton`, `submitButtonFontSize`, `title`, `titleFontSize`, `errorBanner` |
@@ -137,11 +137,28 @@ throw validationError('Email is required', 'email');
137
137
  | `rateLimitError(message)` | Factory for rate limit errors |
138
138
  | `networkError(message)` | Factory for network errors |
139
139
 
140
+ ### Privacy-safe telemetry contract
141
+
142
+ `@flopay/shared` exports the versioned closed telemetry types and builders used
143
+ by both browser packages: `buildTelemetryErrorEvent`,
144
+ `buildTelemetryLogEvent`, `buildTelemetryPerformanceEvent`,
145
+ `buildTelemetryTerminalEvent`, and `serializeTelemetryBatch`. Builders copy
146
+ only allowlisted enum/numeric/opaque fields; arbitrary messages, stacks,
147
+ payloads, URLs, credentials, customer data, and unknown nested fields cannot
148
+ enter serialized events.
149
+
150
+ `TELEMETRY_LOG_NAMES` is the complete SDK-authored lifecycle catalog. The
151
+ serializer emits the backend-owned v1 envelope with the fixed
152
+ the closed package identity (`@flopay/shared`, `@flopay/js`, or
153
+ `@flopay/react`) and event classes. See
154
+ [`docs/TELEMETRY.md`](../../docs/TELEMETRY.md) for the privacy and operational
155
+ policy.
156
+
140
157
  ### Constants
141
158
 
142
159
  | Export | Description |
143
160
  |--------|-------------|
144
- | `SDK_VERSION` | Current SDK version (`'0.1.0'`) |
161
+ | `SDK_VERSION` | Current SDK version (`'1.4.0'`) |
145
162
  | `DEFAULT_API_BASE_URL` | `'https://api.flopay.io'` |
146
163
  | `DEFAULT_API_VERSION` | `'2024-01-01'` |
147
164
  | `DEFAULT_APPEARANCE` | Default theme (primary `#4A49FF`, white background, Poppins font) |
package/dist/index.cjs CHANGED
@@ -64,13 +64,19 @@ __export(index_exports, {
64
64
  MODERN_DARK_APPEARANCE: () => MODERN_DARK_APPEARANCE,
65
65
  MODERN_LIGHT_APPEARANCE: () => MODERN_LIGHT_APPEARANCE,
66
66
  NIGHT_APPEARANCE: () => NIGHT_APPEARANCE,
67
- SDK_VERSION: () => SDK_VERSION,
67
+ SDK_VERSION: () => SDK_VERSION2,
68
68
  STRIPE_EXPRESS_METHODS: () => STRIPE_EXPRESS_METHODS,
69
69
  STRIPE_METHOD_AMOUNT_LIMITS: () => STRIPE_METHOD_AMOUNT_LIMITS,
70
70
  STRIPE_METHOD_COUNTRIES: () => STRIPE_METHOD_COUNTRIES,
71
71
  STRIPE_METHOD_CURRENCIES: () => STRIPE_METHOD_CURRENCIES,
72
72
  STRIPE_METHOD_MATRIX: () => STRIPE_METHOD_MATRIX,
73
73
  SUPPORTED_CARD_BRANDS: () => SUPPORTED_CARD_BRANDS,
74
+ TELEMETRY_ERROR_CODES: () => TELEMETRY_ERROR_CODES,
75
+ TELEMETRY_LOG_NAMES: () => TELEMETRY_LOG_NAMES,
76
+ TELEMETRY_MAX_BATCH_BYTES: () => TELEMETRY_MAX_BATCH_BYTES,
77
+ TELEMETRY_MAX_BATCH_EVENTS: () => TELEMETRY_MAX_BATCH_EVENTS,
78
+ TELEMETRY_SCHEMA_VERSION: () => TELEMETRY_SCHEMA_VERSION,
79
+ TELEMETRY_SDK_PACKAGES: () => TELEMETRY_SDK_PACKAGES,
74
80
  THEMES: () => THEMES,
75
81
  US_STATES: () => US_STATES,
76
82
  apiError: () => apiError,
@@ -79,6 +85,10 @@ __export(index_exports, {
79
85
  buildItemPayload: () => buildItemPayload,
80
86
  buildProductPayload: () => buildProductPayload,
81
87
  buildSubscriptionPayload: () => buildSubscriptionPayload,
88
+ buildTelemetryErrorEvent: () => buildTelemetryErrorEvent,
89
+ buildTelemetryLogEvent: () => buildTelemetryLogEvent,
90
+ buildTelemetryPerformanceEvent: () => buildTelemetryPerformanceEvent,
91
+ buildTelemetryTerminalEvent: () => buildTelemetryTerminalEvent,
82
92
  configureFlopay: () => configureFlopay,
83
93
  filterStripeMethodsByAmount: () => filterStripeMethodsByAmount,
84
94
  filterStripeMethodsByCountry: () => filterStripeMethodsByCountry,
@@ -115,6 +125,7 @@ __export(index_exports, {
115
125
  resolveSessionCurrency: () => resolveSessionCurrency,
116
126
  resolveStripeMethodBrandVariant: () => resolveStripeMethodBrandVariant,
117
127
  resolveTheme: () => resolveTheme,
128
+ serializeTelemetryBatch: () => serializeTelemetryBatch,
118
129
  stripeExpressMethodToOptionKey: () => stripeExpressMethodToOptionKey,
119
130
  validationError: () => validationError
120
131
  });
@@ -167,6 +178,300 @@ function networkError(message) {
167
178
  return new FloPayError(message, "network_error");
168
179
  }
169
180
 
181
+ // src/telemetry.ts
182
+ var TELEMETRY_SCHEMA_VERSION = 1;
183
+ var TELEMETRY_SDK_PACKAGES = [
184
+ "@flopay/shared",
185
+ "@flopay/js",
186
+ "@flopay/react"
187
+ ];
188
+ var TELEMETRY_MAX_BATCH_BYTES = 32 * 1024;
189
+ var TELEMETRY_MAX_BATCH_EVENTS = 20;
190
+ var TELEMETRY_PROVIDERS = ["stripe", "paypal"];
191
+ var TELEMETRY_PAYMENT_METHOD_CATEGORIES = [
192
+ "card",
193
+ "wallet",
194
+ "paypal",
195
+ "apm",
196
+ "saved",
197
+ "unknown"
198
+ ];
199
+ var TELEMETRY_STAGES = [
200
+ "callback",
201
+ "sdk_initialize",
202
+ "checkout_mount",
203
+ "session_create",
204
+ "session_read",
205
+ "session_first_byte",
206
+ "session_complete",
207
+ "checkout_data_ready",
208
+ "checkout_render",
209
+ "checkout_interactive",
210
+ "provider_load",
211
+ "provider_ready",
212
+ "overlay_open",
213
+ "overlay_return",
214
+ "redirect",
215
+ "redirect_resume",
216
+ "vault_request",
217
+ "vault_mount",
218
+ "vault_ready",
219
+ "vault_submit",
220
+ "tokenization",
221
+ "three_ds_handoff",
222
+ "three_ds_return",
223
+ "processing",
224
+ "recovery",
225
+ "completion",
226
+ "unmount",
227
+ "total_journey"
228
+ ];
229
+ var TELEMETRY_ERROR_CODES = [
230
+ "CALLBACK_FAILED",
231
+ "CHECKOUT_SESSION_CREATE_FAILED",
232
+ "CONFIGURATION_INVALID",
233
+ "INTERNAL_SDK_ERROR",
234
+ "NETWORK_REQUEST_FAILED",
235
+ "PAYMENT_PROCESSING_FAILED",
236
+ "POPUP_BLOCKED",
237
+ "PROVIDER_LOAD_FAILED",
238
+ "PROVIDER_RUNTIME_FAILED",
239
+ "RECOVERY_FAILED",
240
+ "REDIRECT_RESUME_FAILED",
241
+ "REQUEST_TIMEOUT",
242
+ "SDK_INITIALIZATION_FAILED",
243
+ "THREE_DS_FAILED",
244
+ "TOKENIZATION_FAILED",
245
+ "VAULT_LOAD_FAILED",
246
+ "VAULT_SUBMIT_FAILED"
247
+ ];
248
+ var TELEMETRY_LOG_NAMES = [
249
+ "sdk.initialize.started",
250
+ "sdk.initialize.ready",
251
+ "sdk.cache.hit",
252
+ "sdk.cache.miss",
253
+ "checkout.mount",
254
+ "session.create.started",
255
+ "session.read.started",
256
+ "session.request.first_byte",
257
+ "session.request.completed",
258
+ "checkout.data.ready",
259
+ "checkout.rendered",
260
+ "checkout.interactive",
261
+ "checkout.recovery.started",
262
+ "checkout.recovery.completed",
263
+ "checkout.unmount",
264
+ "provider.load.started",
265
+ "provider.eligibility.checked",
266
+ "provider.availability.checked",
267
+ "provider.ready",
268
+ "provider.popup.opened",
269
+ "provider.overlay.opened",
270
+ "provider.overlay.returned",
271
+ "provider.redirect.started",
272
+ "provider.redirect.resumed",
273
+ "vault.capture.requested",
274
+ "vault.widget.mounted",
275
+ "vault.widget.ready",
276
+ "vault.submission.started",
277
+ "vault.action.required",
278
+ "vault.three_ds.handoff",
279
+ "vault.three_ds.returned",
280
+ "vault.terminal",
281
+ "payment.method.selected",
282
+ "payment.intent.started",
283
+ "payment.intent.completed",
284
+ "payment.tokenization.started",
285
+ "payment.tokenization.completed",
286
+ "payment.processing.started",
287
+ "payment.processing.completed",
288
+ "payment.three_ds.handoff",
289
+ "payment.three_ds.returned",
290
+ "operation.retry",
291
+ "operation.deduplicated",
292
+ "operation.fallback",
293
+ "operation.cache.hit",
294
+ "operation.cache.miss",
295
+ "operation.recovery.started",
296
+ "operation.recovery.completed",
297
+ "operation.state_transition"
298
+ ];
299
+ var TELEMETRY_REQUEST_CATEGORIES = [
300
+ "session_create",
301
+ "session_read",
302
+ "intent_create",
303
+ "process_payment",
304
+ "vault_capture",
305
+ "account_snapshot",
306
+ "provider_sdk",
307
+ "other"
308
+ ];
309
+ var TELEMETRY_STATUS_CLASSES = [
310
+ "2xx",
311
+ "3xx",
312
+ "4xx",
313
+ "5xx",
314
+ "network_error",
315
+ "timeout",
316
+ "unknown"
317
+ ];
318
+ var TELEMETRY_CHECKOUT_MODES = ["full", "auto", "confirm", "unknown"];
319
+ var TELEMETRY_LAYOUTS = ["embedded", "buttons", "automatic", "unknown"];
320
+ var TELEMETRY_DURATION_MODES = ["machine", "buyer", "total"];
321
+ var TELEMETRY_EXPECTED_OUTCOMES = [
322
+ "action_required",
323
+ "customer_abandoned",
324
+ "payment_cancelled",
325
+ "payment_declined",
326
+ "payment_succeeded",
327
+ "validation_rejected"
328
+ ];
329
+ var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
330
+ var SDK_VERSION = /^\d{1,4}\.\d{1,4}\.\d{1,4}(?:-[0-9A-Za-z.-]{1,32})?$/;
331
+ var ERROR_CODES = new Set(TELEMETRY_ERROR_CODES);
332
+ var STAGES = new Set(TELEMETRY_STAGES);
333
+ var PROVIDERS = new Set(TELEMETRY_PROVIDERS);
334
+ var PAYMENT_METHODS = new Set(TELEMETRY_PAYMENT_METHOD_CATEGORIES);
335
+ var LOG_NAMES = new Set(TELEMETRY_LOG_NAMES);
336
+ var CHECKOUT_MODES = new Set(TELEMETRY_CHECKOUT_MODES);
337
+ var LAYOUTS = new Set(TELEMETRY_LAYOUTS);
338
+ var REQUEST_CATEGORIES = new Set(TELEMETRY_REQUEST_CATEGORIES);
339
+ var STATUS_CLASSES = new Set(TELEMETRY_STATUS_CLASSES);
340
+ var DURATION_MODES = new Set(TELEMETRY_DURATION_MODES);
341
+ var EXPECTED_OUTCOMES = new Set(TELEMETRY_EXPECTED_OUTCOMES);
342
+ var SDK_PACKAGES = new Set(TELEMETRY_SDK_PACKAGES);
343
+ function enumOr(value, allowed, fallback) {
344
+ return typeof value === "string" && allowed.has(value) ? value : fallback;
345
+ }
346
+ function createUuidV4() {
347
+ try {
348
+ const id = globalThis.crypto.randomUUID();
349
+ if (UUID_V4.test(id)) return id;
350
+ } catch {
351
+ }
352
+ const bytes = new Uint8Array(16);
353
+ try {
354
+ globalThis.crypto.getRandomValues(bytes);
355
+ } catch {
356
+ for (let index = 0; index < bytes.length; index += 1) {
357
+ bytes[index] = Math.floor(Math.random() * 256);
358
+ }
359
+ }
360
+ bytes[6] = bytes[6] & 15 | 64;
361
+ bytes[8] = bytes[8] & 63 | 128;
362
+ const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
363
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
364
+ }
365
+ function safeUuid(value) {
366
+ return typeof value === "string" && UUID_V4.test(value) ? value : createUuidV4();
367
+ }
368
+ function safeAttempt(value) {
369
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 100 ? value : void 0;
370
+ }
371
+ function safeSequence(value) {
372
+ return Number.isSafeInteger(value) && value >= 0 ? Math.min(value, 1e6) : 0;
373
+ }
374
+ function safeDuration(value) {
375
+ if (!Number.isFinite(value) || value < 0) return 0;
376
+ return Math.min(Math.round(value), 12e4);
377
+ }
378
+ function addCommonFields(event, input) {
379
+ const attempt = safeAttempt(input.attempt);
380
+ if (input.provider && PROVIDERS.has(input.provider)) event.provider = input.provider;
381
+ if (attempt !== void 0) event.attempt = attempt;
382
+ if (input.statusClass) event.statusClass = enumOr(input.statusClass, STATUS_CLASSES, "unknown");
383
+ if (input.requestCategory) {
384
+ event.requestCategory = enumOr(input.requestCategory, REQUEST_CATEGORIES, "other");
385
+ }
386
+ if (input.paymentMethodCategory) {
387
+ event.paymentMethodCategory = enumOr(
388
+ input.paymentMethodCategory,
389
+ PAYMENT_METHODS,
390
+ "unknown"
391
+ );
392
+ }
393
+ if (input.checkoutMode) {
394
+ event.checkoutMode = enumOr(input.checkoutMode, CHECKOUT_MODES, "unknown");
395
+ }
396
+ if (input.layout) event.layout = enumOr(input.layout, LAYOUTS, "unknown");
397
+ return event;
398
+ }
399
+ function buildTelemetryErrorEvent(input) {
400
+ return addCommonFields(
401
+ {
402
+ eventId: safeUuid(input.eventId),
403
+ class: "technical_error",
404
+ code: enumOr(input.errorCode, ERROR_CODES, "INTERNAL_SDK_ERROR"),
405
+ stage: enumOr(input.stage, STAGES, "sdk_initialize"),
406
+ sequence: safeSequence(input.sequence)
407
+ },
408
+ input
409
+ );
410
+ }
411
+ function buildTelemetryLogEvent(input) {
412
+ return addCommonFields(
413
+ {
414
+ eventId: safeUuid(input.eventId),
415
+ class: "lifecycle",
416
+ name: enumOr(input.name, LOG_NAMES, "operation.retry"),
417
+ stage: enumOr(input.stage, STAGES, "sdk_initialize"),
418
+ sequence: safeSequence(input.sequence)
419
+ },
420
+ input
421
+ );
422
+ }
423
+ function buildTelemetryTerminalEvent(input) {
424
+ const commonInput = {
425
+ ...input,
426
+ stage: input.stage ?? "completion"
427
+ };
428
+ return addCommonFields(
429
+ {
430
+ eventId: safeUuid(input.eventId),
431
+ class: "expected_outcome",
432
+ outcome: enumOr(input.outcome, EXPECTED_OUTCOMES, "customer_abandoned"),
433
+ stage: enumOr(input.stage, STAGES, "completion"),
434
+ sequence: safeSequence(input.sequence)
435
+ },
436
+ commonInput
437
+ );
438
+ }
439
+ function buildTelemetryPerformanceEvent(input) {
440
+ return addCommonFields(
441
+ {
442
+ eventId: safeUuid(input.eventId),
443
+ class: "performance",
444
+ stage: enumOr(input.stage, STAGES, "sdk_initialize"),
445
+ sequence: safeSequence(input.sequence),
446
+ durationMs: safeDuration(input.durationMs),
447
+ durationMode: enumOr(input.durationMode, DURATION_MODES, "machine")
448
+ },
449
+ input
450
+ );
451
+ }
452
+ function serializeTelemetryBatch(events, options) {
453
+ const sanitizedEvents = events.slice(0, TELEMETRY_MAX_BATCH_EVENTS).flatMap((event) => {
454
+ if (event.class === "technical_error") {
455
+ return [buildTelemetryErrorEvent({ ...event, errorCode: event.code })];
456
+ }
457
+ if (event.class === "lifecycle") return [buildTelemetryLogEvent(event)];
458
+ if (event.class === "expected_outcome") return [buildTelemetryTerminalEvent(event)];
459
+ if (event.class === "performance") return [buildTelemetryPerformanceEvent(event)];
460
+ return [];
461
+ });
462
+ const envelope = {
463
+ schemaVersion: TELEMETRY_SCHEMA_VERSION,
464
+ batchId: safeUuid(options.batchId),
465
+ correlationId: safeUuid(options.correlationId),
466
+ sdk: {
467
+ package: enumOr(options.sdkPackage, SDK_PACKAGES, "@flopay/shared"),
468
+ version: SDK_VERSION.test(options.sdkVersion) ? options.sdkVersion : "0.0.0"
469
+ },
470
+ events: sanitizedEvents
471
+ };
472
+ return JSON.stringify(envelope);
473
+ }
474
+
170
475
  // src/config.ts
171
476
  var ENV_URL_MAP = {
172
477
  local: "https://flo.ngrok.pro",
@@ -201,7 +506,7 @@ var PAYMENT_METHOD_LOGOS = {
201
506
  };
202
507
 
203
508
  // src/constants.ts
204
- var SDK_VERSION = "1.3.3";
509
+ var SDK_VERSION2 = "1.4.0";
205
510
  var FLO_SDK_VERSION_HEADER = "x-flo-sdk-version";
206
511
  var BILLING_API_URL_STAGING = "https://api.stage.flopay.com";
207
512
  var BILLING_API_URL_PRODUCTION = "https://api.flopay.com";
@@ -2365,6 +2670,12 @@ function resolveIdempotencyKey(supplied) {
2365
2670
  STRIPE_METHOD_CURRENCIES,
2366
2671
  STRIPE_METHOD_MATRIX,
2367
2672
  SUPPORTED_CARD_BRANDS,
2673
+ TELEMETRY_ERROR_CODES,
2674
+ TELEMETRY_LOG_NAMES,
2675
+ TELEMETRY_MAX_BATCH_BYTES,
2676
+ TELEMETRY_MAX_BATCH_EVENTS,
2677
+ TELEMETRY_SCHEMA_VERSION,
2678
+ TELEMETRY_SDK_PACKAGES,
2368
2679
  THEMES,
2369
2680
  US_STATES,
2370
2681
  apiError,
@@ -2373,6 +2684,10 @@ function resolveIdempotencyKey(supplied) {
2373
2684
  buildItemPayload,
2374
2685
  buildProductPayload,
2375
2686
  buildSubscriptionPayload,
2687
+ buildTelemetryErrorEvent,
2688
+ buildTelemetryLogEvent,
2689
+ buildTelemetryPerformanceEvent,
2690
+ buildTelemetryTerminalEvent,
2376
2691
  configureFlopay,
2377
2692
  filterStripeMethodsByAmount,
2378
2693
  filterStripeMethodsByCountry,
@@ -2409,6 +2724,7 @@ function resolveIdempotencyKey(supplied) {
2409
2724
  resolveSessionCurrency,
2410
2725
  resolveStripeMethodBrandVariant,
2411
2726
  resolveTheme,
2727
+ serializeTelemetryBatch,
2412
2728
  stripeExpressMethodToOptionKey,
2413
2729
  validationError
2414
2730
  });