@flopay/js 1.4.3 → 1.4.5
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/index.cjs +1 -3759
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.mjs +1 -3757
- package/package.json +4 -2
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,3759 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
FloPay: () => FloPay,
|
|
24
|
-
FloPayElements: () => FloPayElements,
|
|
25
|
-
PaymentAPI: () => PaymentAPI,
|
|
26
|
-
PciVaultCardCapture: () => PciVaultCardCapture,
|
|
27
|
-
StripeAdapter: () => StripeAdapter,
|
|
28
|
-
cacheSessionDisplayData: () => cacheSessionDisplayData,
|
|
29
|
-
clearSessionDisplayData: () => clearSessionDisplayData,
|
|
30
|
-
createCheckoutSession: () => createCheckoutSession,
|
|
31
|
-
createCheckoutSessionWithRetries: () => createCheckoutSessionWithRetries,
|
|
32
|
-
getSessionDisplayData: () => getSessionDisplayData,
|
|
33
|
-
loadFloPay: () => loadFloPay
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(index_exports);
|
|
36
|
-
|
|
37
|
-
// src/load.ts
|
|
38
|
-
var import_shared7 = require("@flopay/shared");
|
|
39
|
-
|
|
40
|
-
// src/stripe-adapter.ts
|
|
41
|
-
var import_stripe_js = require("@stripe/stripe-js");
|
|
42
|
-
var import_shared3 = require("@flopay/shared");
|
|
43
|
-
|
|
44
|
-
// src/payment-api.ts
|
|
45
|
-
var import_shared2 = require("@flopay/shared");
|
|
46
|
-
|
|
47
|
-
// src/api-error.ts
|
|
48
|
-
function readErrorString(value) {
|
|
49
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
50
|
-
}
|
|
51
|
-
function readErrorMessage(value) {
|
|
52
|
-
if (typeof value === "string") return value.trim() ? value : void 0;
|
|
53
|
-
if (Array.isArray(value)) {
|
|
54
|
-
const joined = value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).join("; ");
|
|
55
|
-
return joined || void 0;
|
|
56
|
-
}
|
|
57
|
-
return void 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// src/session-display-cache.ts
|
|
61
|
-
var STORAGE_KEY_PREFIX = "flopay_session_display:";
|
|
62
|
-
var DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
63
|
-
var memoryStore = /* @__PURE__ */ new Map();
|
|
64
|
-
function storageKey(sessionId) {
|
|
65
|
-
return `${STORAGE_KEY_PREFIX}${sessionId}`;
|
|
66
|
-
}
|
|
67
|
-
function getSessionStorage() {
|
|
68
|
-
if (typeof window === "undefined") return null;
|
|
69
|
-
try {
|
|
70
|
-
return window.sessionStorage;
|
|
71
|
-
} catch {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function cacheSessionDisplayData(sessionId, data, options) {
|
|
76
|
-
if (!sessionId) return;
|
|
77
|
-
const ttl = options?.ttlMs ?? DEFAULT_TTL_MS;
|
|
78
|
-
const entry = { data, expiresAt: Date.now() + ttl };
|
|
79
|
-
const storage = getSessionStorage();
|
|
80
|
-
if (storage) {
|
|
81
|
-
try {
|
|
82
|
-
storage.setItem(storageKey(sessionId), JSON.stringify(entry));
|
|
83
|
-
return;
|
|
84
|
-
} catch {
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
memoryStore.set(sessionId, entry);
|
|
88
|
-
}
|
|
89
|
-
function getSessionDisplayData(sessionId) {
|
|
90
|
-
if (!sessionId) return null;
|
|
91
|
-
const storage = getSessionStorage();
|
|
92
|
-
if (storage) {
|
|
93
|
-
try {
|
|
94
|
-
const raw = storage.getItem(storageKey(sessionId));
|
|
95
|
-
if (raw) {
|
|
96
|
-
const entry = JSON.parse(raw);
|
|
97
|
-
if (entry && typeof entry.expiresAt === "number" && entry.expiresAt > Date.now()) {
|
|
98
|
-
return entry.data;
|
|
99
|
-
}
|
|
100
|
-
storage.removeItem(storageKey(sessionId));
|
|
101
|
-
}
|
|
102
|
-
} catch {
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const memEntry = memoryStore.get(sessionId);
|
|
106
|
-
if (memEntry) {
|
|
107
|
-
if (memEntry.expiresAt > Date.now()) {
|
|
108
|
-
return memEntry.data;
|
|
109
|
-
}
|
|
110
|
-
memoryStore.delete(sessionId);
|
|
111
|
-
}
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
function clearSessionDisplayData(sessionId) {
|
|
115
|
-
if (!sessionId) return;
|
|
116
|
-
memoryStore.delete(sessionId);
|
|
117
|
-
const storage = getSessionStorage();
|
|
118
|
-
if (storage) {
|
|
119
|
-
try {
|
|
120
|
-
storage.removeItem(storageKey(sessionId));
|
|
121
|
-
} catch {
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/telemetry-reporter.ts
|
|
127
|
-
var import_shared = require("@flopay/shared");
|
|
128
|
-
var TELEMETRY_PATH = "/v1/sdk-telemetry/events";
|
|
129
|
-
var MAX_BATCH_SIZE = 16;
|
|
130
|
-
var MAX_QUEUE_SIZE = 64;
|
|
131
|
-
var UPLOAD_TIMEOUT_MS = 1500;
|
|
132
|
-
var ERROR_DEDUPLICATION_WINDOW_MS = 1e3;
|
|
133
|
-
var MAX_REPORTED_FAILURES = 64;
|
|
134
|
-
var DEDUPLICATION_EVENT_ID = "00000000-0000-4000-8000-000000000000";
|
|
135
|
-
var EVENT_BUDGETS = {
|
|
136
|
-
technical_error: 8,
|
|
137
|
-
lifecycle: 32,
|
|
138
|
-
expected_outcome: 32,
|
|
139
|
-
performance: 24
|
|
140
|
-
};
|
|
141
|
-
function createUuidV4() {
|
|
142
|
-
try {
|
|
143
|
-
return globalThis.crypto.randomUUID();
|
|
144
|
-
} catch {
|
|
145
|
-
const bytes = new Uint8Array(16);
|
|
146
|
-
try {
|
|
147
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
148
|
-
} catch {
|
|
149
|
-
for (let index = 0; index < bytes.length; index += 1) {
|
|
150
|
-
bytes[index] = Math.floor(Math.random() * 256);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
bytes[6] = bytes[6] & 15 | 64;
|
|
154
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
155
|
-
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
156
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function bodyByteLength(body) {
|
|
160
|
-
try {
|
|
161
|
-
return new TextEncoder().encode(body).byteLength;
|
|
162
|
-
} catch {
|
|
163
|
-
return body.length;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
function failureDeduplicationKey(event) {
|
|
167
|
-
return JSON.stringify([
|
|
168
|
-
event.code,
|
|
169
|
-
event.stage,
|
|
170
|
-
event.provider,
|
|
171
|
-
event.attempt,
|
|
172
|
-
event.statusClass,
|
|
173
|
-
event.requestCategory,
|
|
174
|
-
event.paymentMethodCategory,
|
|
175
|
-
event.checkoutMode,
|
|
176
|
-
event.layout
|
|
177
|
-
]);
|
|
178
|
-
}
|
|
179
|
-
function telemetryNow() {
|
|
180
|
-
return globalThis.performance?.now() ?? 0;
|
|
181
|
-
}
|
|
182
|
-
var TelemetryReporter = class {
|
|
183
|
-
constructor(options) {
|
|
184
|
-
this.ingestionDisabled = false;
|
|
185
|
-
this.queue = [];
|
|
186
|
-
this.sequence = 0;
|
|
187
|
-
this.flushTimer = null;
|
|
188
|
-
this.flushInFlight = null;
|
|
189
|
-
this.reportedFailures = /* @__PURE__ */ new Map();
|
|
190
|
-
this.checkoutContext = {};
|
|
191
|
-
this.checkoutStartedAt = null;
|
|
192
|
-
this.destroyed = false;
|
|
193
|
-
this.pageExitHandler = () => {
|
|
194
|
-
this.drainQueue();
|
|
195
|
-
};
|
|
196
|
-
this.visibilityHandler = () => {
|
|
197
|
-
if (document.visibilityState === "hidden") void this.flush();
|
|
198
|
-
};
|
|
199
|
-
this.eventCounts = {
|
|
200
|
-
technical_error: 0,
|
|
201
|
-
lifecycle: 0,
|
|
202
|
-
expected_outcome: 0,
|
|
203
|
-
performance: 0
|
|
204
|
-
};
|
|
205
|
-
this.endpoint = `${options.billingApiUrl.replace(/\/+$/, "")}${TELEMETRY_PATH}`;
|
|
206
|
-
this.sdkPackage = options.sdkPackage ?? "@flopay/js";
|
|
207
|
-
this.sdkVersion = options.sdkVersion;
|
|
208
|
-
this.correlationId = createUuidV4();
|
|
209
|
-
this.merchantEnabled = options.enabled !== false;
|
|
210
|
-
this.clock = options.clock ?? telemetryNow;
|
|
211
|
-
this.browserTransportAvailable = typeof window !== "undefined" && typeof document !== "undefined";
|
|
212
|
-
if (this.browserTransportAvailable) {
|
|
213
|
-
window.addEventListener("pagehide", this.pageExitHandler);
|
|
214
|
-
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
log(input) {
|
|
218
|
-
if (!this.canCollect()) return;
|
|
219
|
-
this.enqueue((0, import_shared.buildTelemetryLogEvent)({
|
|
220
|
-
...this.checkoutContext,
|
|
221
|
-
...input,
|
|
222
|
-
eventId: createUuidV4(),
|
|
223
|
-
sequence: this.sequence++
|
|
224
|
-
}));
|
|
225
|
-
}
|
|
226
|
-
error(input) {
|
|
227
|
-
if (!this.canCollect()) return;
|
|
228
|
-
const normalizedFailure = (0, import_shared.buildTelemetryErrorEvent)({
|
|
229
|
-
...this.checkoutContext,
|
|
230
|
-
...input,
|
|
231
|
-
eventId: DEDUPLICATION_EVENT_ID,
|
|
232
|
-
sequence: 0
|
|
233
|
-
});
|
|
234
|
-
const deduplicationKey = failureDeduplicationKey(normalizedFailure);
|
|
235
|
-
const now = this.now();
|
|
236
|
-
this.pruneReportedFailures(now);
|
|
237
|
-
const previouslyReportedAt = this.reportedFailures.get(deduplicationKey);
|
|
238
|
-
if (previouslyReportedAt !== void 0 && now >= previouslyReportedAt && now - previouslyReportedAt < ERROR_DEDUPLICATION_WINDOW_MS) {
|
|
239
|
-
this.log({
|
|
240
|
-
name: "operation.deduplicated",
|
|
241
|
-
stage: input.stage,
|
|
242
|
-
provider: input.provider,
|
|
243
|
-
paymentMethodCategory: input.paymentMethodCategory,
|
|
244
|
-
attempt: input.attempt
|
|
245
|
-
});
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
this.rememberReportedFailure(deduplicationKey, now);
|
|
249
|
-
this.enqueue((0, import_shared.buildTelemetryErrorEvent)({
|
|
250
|
-
...this.checkoutContext,
|
|
251
|
-
...input,
|
|
252
|
-
eventId: createUuidV4(),
|
|
253
|
-
sequence: this.sequence++
|
|
254
|
-
}));
|
|
255
|
-
}
|
|
256
|
-
performance(input) {
|
|
257
|
-
if (!this.canCollect()) return;
|
|
258
|
-
this.enqueue((0, import_shared.buildTelemetryPerformanceEvent)({
|
|
259
|
-
...this.checkoutContext,
|
|
260
|
-
...input,
|
|
261
|
-
eventId: createUuidV4(),
|
|
262
|
-
sequence: this.sequence++
|
|
263
|
-
}));
|
|
264
|
-
}
|
|
265
|
-
terminal(input) {
|
|
266
|
-
if (!this.canCollect()) return;
|
|
267
|
-
this.enqueue((0, import_shared.buildTelemetryTerminalEvent)({
|
|
268
|
-
...this.checkoutContext,
|
|
269
|
-
...input,
|
|
270
|
-
eventId: createUuidV4(),
|
|
271
|
-
sequence: this.sequence++
|
|
272
|
-
}));
|
|
273
|
-
if (input.outcome !== "action_required" && this.checkoutStartedAt !== null) {
|
|
274
|
-
const checkoutStartedAt = this.checkoutStartedAt;
|
|
275
|
-
this.checkoutStartedAt = null;
|
|
276
|
-
this.performance({
|
|
277
|
-
stage: "total_journey",
|
|
278
|
-
durationMs: Math.max(0, this.now() - checkoutStartedAt),
|
|
279
|
-
durationMode: "total",
|
|
280
|
-
provider: input.provider,
|
|
281
|
-
paymentMethodCategory: input.paymentMethodCategory
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
/** @internal Read the reporter's monotonic clock without Resource Timing. */
|
|
286
|
-
now() {
|
|
287
|
-
try {
|
|
288
|
-
return this.clock();
|
|
289
|
-
} catch {
|
|
290
|
-
return telemetryNow();
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
pruneReportedFailures(now) {
|
|
294
|
-
for (const [key, reportedAt] of this.reportedFailures) {
|
|
295
|
-
if (now < reportedAt || now - reportedAt >= ERROR_DEDUPLICATION_WINDOW_MS) {
|
|
296
|
-
this.reportedFailures.delete(key);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
rememberReportedFailure(key, reportedAt) {
|
|
301
|
-
while (this.reportedFailures.size >= MAX_REPORTED_FAILURES) {
|
|
302
|
-
const oldest = this.reportedFailures.keys().next();
|
|
303
|
-
if (oldest.done) break;
|
|
304
|
-
this.reportedFailures.delete(oldest.value);
|
|
305
|
-
}
|
|
306
|
-
this.reportedFailures.set(key, reportedAt);
|
|
307
|
-
}
|
|
308
|
-
/** @internal Add closed checkout dimensions to subsequent SDK events. */
|
|
309
|
-
setCheckoutContext(context) {
|
|
310
|
-
this.checkoutContext = {
|
|
311
|
-
checkoutMode: context.checkoutMode,
|
|
312
|
-
layout: context.layout
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
/** @internal Start a fresh checkout budget, dedupe window, and total span. */
|
|
316
|
-
beginCheckout(context = {}) {
|
|
317
|
-
if (!this.canCollect()) return 0;
|
|
318
|
-
this.drainQueue();
|
|
319
|
-
this.setCheckoutContext(context);
|
|
320
|
-
this.sequence = 0;
|
|
321
|
-
this.reportedFailures.clear();
|
|
322
|
-
this.eventCounts = {
|
|
323
|
-
technical_error: 0,
|
|
324
|
-
lifecycle: 0,
|
|
325
|
-
expected_outcome: 0,
|
|
326
|
-
performance: 0
|
|
327
|
-
};
|
|
328
|
-
this.checkoutStartedAt = this.now();
|
|
329
|
-
return this.checkoutStartedAt;
|
|
330
|
-
}
|
|
331
|
-
enqueue(event) {
|
|
332
|
-
if (!this.canCollect()) return;
|
|
333
|
-
if (this.queue.length >= MAX_QUEUE_SIZE || this.eventCounts[event.class] >= EVENT_BUDGETS[event.class]) return;
|
|
334
|
-
this.eventCounts[event.class] += 1;
|
|
335
|
-
this.queue.push(event);
|
|
336
|
-
if (this.queue.length >= MAX_BATCH_SIZE) {
|
|
337
|
-
void this.flush();
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
this.scheduleFlush();
|
|
341
|
-
}
|
|
342
|
-
canCollect() {
|
|
343
|
-
return this.browserTransportAvailable && this.merchantEnabled && !this.ingestionDisabled && !this.destroyed;
|
|
344
|
-
}
|
|
345
|
-
/** Flush one bounded batch. Failures are intentionally dropped. */
|
|
346
|
-
async flush() {
|
|
347
|
-
if (this.flushInFlight) return this.flushInFlight;
|
|
348
|
-
if (!this.browserTransportAvailable || this.destroyed || this.ingestionDisabled || this.queue.length === 0) return;
|
|
349
|
-
this.clearFlushTimer();
|
|
350
|
-
const events = this.queue.splice(0, MAX_BATCH_SIZE);
|
|
351
|
-
this.flushInFlight = this.sendBatch(events).finally(() => {
|
|
352
|
-
this.flushInFlight = null;
|
|
353
|
-
if (this.queue.length > 0) this.scheduleFlush();
|
|
354
|
-
});
|
|
355
|
-
return this.flushInFlight;
|
|
356
|
-
}
|
|
357
|
-
/** Flush pending work and detach browser lifecycle listeners. */
|
|
358
|
-
destroy() {
|
|
359
|
-
if (this.destroyed) return;
|
|
360
|
-
this.drainQueue();
|
|
361
|
-
this.destroyed = true;
|
|
362
|
-
this.clearFlushTimer();
|
|
363
|
-
if (this.browserTransportAvailable) {
|
|
364
|
-
window.removeEventListener("pagehide", this.pageExitHandler);
|
|
365
|
-
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
366
|
-
}
|
|
367
|
-
this.queue.splice(0);
|
|
368
|
-
this.reportedFailures.clear();
|
|
369
|
-
}
|
|
370
|
-
/** Permanently honor a merchant opt-out and discard queued events. */
|
|
371
|
-
disable() {
|
|
372
|
-
this.merchantEnabled = false;
|
|
373
|
-
this.queue.splice(0);
|
|
374
|
-
this.reportedFailures.clear();
|
|
375
|
-
this.clearFlushTimer();
|
|
376
|
-
}
|
|
377
|
-
/** Start every bounded keepalive request synchronously before page teardown. */
|
|
378
|
-
drainQueue() {
|
|
379
|
-
if (!this.browserTransportAvailable || this.destroyed || this.ingestionDisabled || this.queue.length === 0) return;
|
|
380
|
-
this.clearFlushTimer();
|
|
381
|
-
while (this.queue.length > 0) {
|
|
382
|
-
const events = this.queue.splice(0, MAX_BATCH_SIZE);
|
|
383
|
-
void this.sendBatch(events);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
async sendBatch(events) {
|
|
387
|
-
if (!this.browserTransportAvailable || this.ingestionDisabled) return;
|
|
388
|
-
const body = (0, import_shared.serializeTelemetryBatch)(events, {
|
|
389
|
-
correlationId: this.correlationId,
|
|
390
|
-
sdkPackage: this.sdkPackage,
|
|
391
|
-
sdkVersion: this.sdkVersion,
|
|
392
|
-
batchId: createUuidV4()
|
|
393
|
-
});
|
|
394
|
-
if (bodyByteLength(body) > import_shared.TELEMETRY_MAX_BATCH_BYTES) return;
|
|
395
|
-
const controller = typeof AbortController === "undefined" ? null : new AbortController();
|
|
396
|
-
let timeout = null;
|
|
397
|
-
try {
|
|
398
|
-
const request = fetch(this.endpoint, {
|
|
399
|
-
method: "POST",
|
|
400
|
-
headers: { "content-type": "text/plain;charset=UTF-8" },
|
|
401
|
-
body,
|
|
402
|
-
credentials: "omit",
|
|
403
|
-
keepalive: true,
|
|
404
|
-
referrerPolicy: "no-referrer",
|
|
405
|
-
signal: controller?.signal
|
|
406
|
-
}).then(async (response) => {
|
|
407
|
-
if (response.status !== 202) return null;
|
|
408
|
-
const payload = await response.json().catch(() => null);
|
|
409
|
-
return payload?.status === "disabled" ? "disabled" : null;
|
|
410
|
-
}).catch(() => null);
|
|
411
|
-
const expired = new Promise((resolve) => {
|
|
412
|
-
timeout = setTimeout(() => {
|
|
413
|
-
controller?.abort();
|
|
414
|
-
resolve(null);
|
|
415
|
-
}, UPLOAD_TIMEOUT_MS);
|
|
416
|
-
});
|
|
417
|
-
const status = await Promise.race([request, expired]);
|
|
418
|
-
if (status === "disabled") this.disableFromIngestion();
|
|
419
|
-
} catch {
|
|
420
|
-
} finally {
|
|
421
|
-
if (timeout) clearTimeout(timeout);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
disableFromIngestion() {
|
|
425
|
-
this.ingestionDisabled = true;
|
|
426
|
-
this.queue.splice(0);
|
|
427
|
-
this.reportedFailures.clear();
|
|
428
|
-
this.clearFlushTimer();
|
|
429
|
-
}
|
|
430
|
-
scheduleFlush() {
|
|
431
|
-
if (this.flushTimer || this.destroyed || this.ingestionDisabled) return;
|
|
432
|
-
this.flushTimer = setTimeout(() => {
|
|
433
|
-
this.flushTimer = null;
|
|
434
|
-
void this.flush();
|
|
435
|
-
}, 0);
|
|
436
|
-
}
|
|
437
|
-
clearFlushTimer() {
|
|
438
|
-
if (!this.flushTimer) return;
|
|
439
|
-
clearTimeout(this.flushTimer);
|
|
440
|
-
this.flushTimer = null;
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
var TELEMETRY_REPORTER_FACTORY = /* @__PURE__ */ Symbol.for("@flopay/js.telemetry.reporter-factory.v1");
|
|
444
|
-
var telemetryGlobal = globalThis;
|
|
445
|
-
if (telemetryGlobal[TELEMETRY_REPORTER_FACTORY] === void 0) {
|
|
446
|
-
Object.defineProperty(telemetryGlobal, TELEMETRY_REPORTER_FACTORY, {
|
|
447
|
-
configurable: true,
|
|
448
|
-
enumerable: false,
|
|
449
|
-
writable: false,
|
|
450
|
-
value: (options) => new TelemetryReporter(options)
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// src/payment-api.ts
|
|
455
|
-
var DEFAULT_PROCESSING_RETRY_AFTER_MS = 1e3;
|
|
456
|
-
var MIN_PROCESSING_RETRY_AFTER_MS = 500;
|
|
457
|
-
var DEFAULT_PROCESSING_TIMEOUT_MS = 15e3;
|
|
458
|
-
var MAX_PROCESSING_RETRY_AFTER_MS = 3e3;
|
|
459
|
-
var DEFAULT_ACCOUNT_SNAPSHOT_TIMEOUT_MS = 1e4;
|
|
460
|
-
function isRecord(value) {
|
|
461
|
-
return typeof value === "object" && value !== null;
|
|
462
|
-
}
|
|
463
|
-
function telemetryStatusClass(status) {
|
|
464
|
-
if (status === void 0) return "network_error";
|
|
465
|
-
const statusClass = `${Math.floor(status / 100)}xx`;
|
|
466
|
-
return statusClass === "2xx" || statusClass === "3xx" || statusClass === "4xx" || statusClass === "5xx" ? statusClass : "unknown";
|
|
467
|
-
}
|
|
468
|
-
function telemetryFailure(error, fallbackCode) {
|
|
469
|
-
if (error instanceof Error && (error.name === "AbortError" || error instanceof import_shared2.FloPayError && error.code === "checkout_processing_timeout")) {
|
|
470
|
-
return { errorCode: "REQUEST_TIMEOUT", statusClass: "timeout" };
|
|
471
|
-
}
|
|
472
|
-
if (error instanceof TypeError) {
|
|
473
|
-
return { errorCode: "NETWORK_REQUEST_FAILED", statusClass: "network_error" };
|
|
474
|
-
}
|
|
475
|
-
return {
|
|
476
|
-
errorCode: fallbackCode,
|
|
477
|
-
statusClass: telemetryStatusClass(
|
|
478
|
-
error instanceof import_shared2.FloPayError ? error.statusCode : void 0
|
|
479
|
-
)
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
function readString(payload, key) {
|
|
483
|
-
return readErrorString(payload?.[key]);
|
|
484
|
-
}
|
|
485
|
-
function readMessage(payload, key) {
|
|
486
|
-
return readErrorMessage(payload?.[key]);
|
|
487
|
-
}
|
|
488
|
-
function readNumber(payload, key) {
|
|
489
|
-
const value = payload?.[key];
|
|
490
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
491
|
-
}
|
|
492
|
-
function delay(ms) {
|
|
493
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
494
|
-
}
|
|
495
|
-
function createCheckoutProcessingTimeoutError() {
|
|
496
|
-
return new import_shared2.FloPayError(
|
|
497
|
-
"Checkout is still processing. Please try again shortly.",
|
|
498
|
-
"api_error",
|
|
499
|
-
{ code: "checkout_processing_timeout" }
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
async function buildApiErrorFromResponse(response, fallbackMessage) {
|
|
503
|
-
const payload = await response.json().catch(() => null);
|
|
504
|
-
const nestedError = isRecord(payload?.error) ? payload.error : null;
|
|
505
|
-
const message = readMessage(payload, "message") ?? readMessage(nestedError, "message") ?? fallbackMessage;
|
|
506
|
-
const code = readString(payload, "code") ?? readString(payload, "gatewayErrorCode") ?? readString(nestedError, "code") ?? `http_${response.status}`;
|
|
507
|
-
return new import_shared2.FloPayError(message, "api_error", {
|
|
508
|
-
code,
|
|
509
|
-
statusCode: response.status
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
var NETWORK_RETRY_ATTEMPTS = 2;
|
|
513
|
-
var IDEMPOTENCY_IN_PROGRESS_RETRY_ATTEMPTS = 2;
|
|
514
|
-
async function fetchWithNetworkRetry(input, init, attempts = NETWORK_RETRY_ATTEMPTS, onRetry) {
|
|
515
|
-
let lastErr;
|
|
516
|
-
for (let attempt = 0; ; attempt++) {
|
|
517
|
-
try {
|
|
518
|
-
return await fetch(input, init);
|
|
519
|
-
} catch (err) {
|
|
520
|
-
if (err instanceof Error && err.name === "AbortError") throw err;
|
|
521
|
-
lastErr = err;
|
|
522
|
-
if (attempt >= attempts) throw lastErr;
|
|
523
|
-
try {
|
|
524
|
-
onRetry?.(attempt + 1);
|
|
525
|
-
} catch {
|
|
526
|
-
}
|
|
527
|
-
await delay(150 * 2 ** attempt);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
function isPaymentApiTelemetryHooks(value) {
|
|
532
|
-
return "now" in value || "onFirstByte" in value || "onSessionCreateFailure" in value || "onRetry" in value;
|
|
533
|
-
}
|
|
534
|
-
var PaymentAPI = class {
|
|
535
|
-
constructor(billingApiUrl, telemetryOptionsOrHooks = {}) {
|
|
536
|
-
this.baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
537
|
-
const hasInternalHooks = isPaymentApiTelemetryHooks(telemetryOptionsOrHooks);
|
|
538
|
-
this.telemetryHooks = hasInternalHooks ? telemetryOptionsOrHooks : void 0;
|
|
539
|
-
this.directTelemetry = hasInternalHooks || telemetryOptionsOrHooks.telemetry === false ? void 0 : new TelemetryReporter({
|
|
540
|
-
billingApiUrl: this.baseUrl,
|
|
541
|
-
sdkVersion: import_shared2.SDK_VERSION
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
/** Dispose the reporter owned by direct public usage. Internal hooks are never disposed here. */
|
|
545
|
-
destroy() {
|
|
546
|
-
this.directTelemetry?.destroy();
|
|
547
|
-
}
|
|
548
|
-
reportDirectFailure(error, fallbackCode, stage, requestCategory, paymentMethodCategory = "unknown") {
|
|
549
|
-
const failure = telemetryFailure(error, fallbackCode);
|
|
550
|
-
this.directTelemetry?.error({
|
|
551
|
-
...failure,
|
|
552
|
-
stage,
|
|
553
|
-
requestCategory,
|
|
554
|
-
paymentMethodCategory
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
telemetryTimestamp() {
|
|
558
|
-
try {
|
|
559
|
-
return this.telemetryHooks?.now?.() ?? this.directTelemetry?.now() ?? telemetryNow();
|
|
560
|
-
} catch {
|
|
561
|
-
return telemetryNow();
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
beginDirectTelemetryCheckout(checkoutSessionId) {
|
|
565
|
-
if (!this.directTelemetry || this.directTelemetryCheckoutId === checkoutSessionId) return;
|
|
566
|
-
this.directTelemetryCheckoutId = checkoutSessionId;
|
|
567
|
-
this.directTelemetry.beginCheckout();
|
|
568
|
-
}
|
|
569
|
-
beginDirectTelemetryOperation() {
|
|
570
|
-
if (!this.directTelemetry) return;
|
|
571
|
-
this.directTelemetryCheckoutId = void 0;
|
|
572
|
-
this.directTelemetry.beginCheckout();
|
|
573
|
-
}
|
|
574
|
-
adoptDirectTelemetryCheckout(checkoutSessionId) {
|
|
575
|
-
if (checkoutSessionId) this.directTelemetryCheckoutId = checkoutSessionId;
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Fetch a raw checkout session by ID.
|
|
579
|
-
*
|
|
580
|
-
* `nonce` is the session-bound checkout token returned when the session
|
|
581
|
-
* was created. When supplied it is sent as the `x-checkout-session-token`
|
|
582
|
-
* header that post-#640 backends match against `checkout_session.nonce`
|
|
583
|
-
* before returning the row — the UUID alone is no longer sufficient.
|
|
584
|
-
* Backends that don't yet enforce it ignore the extra header.
|
|
585
|
-
*/
|
|
586
|
-
async getCheckoutSession(checkoutSessionId, nonce) {
|
|
587
|
-
this.beginDirectTelemetryCheckout(checkoutSessionId);
|
|
588
|
-
const requestStarted = this.telemetryTimestamp();
|
|
589
|
-
this.directTelemetry?.log({
|
|
590
|
-
name: "session.read.started",
|
|
591
|
-
stage: "session_read",
|
|
592
|
-
requestCategory: "session_read"
|
|
593
|
-
});
|
|
594
|
-
const headers = { [import_shared2.FLO_SDK_VERSION_HEADER]: import_shared2.SDK_VERSION };
|
|
595
|
-
if (nonce) headers["x-checkout-session-token"] = nonce;
|
|
596
|
-
try {
|
|
597
|
-
const response = await fetchWithNetworkRetry(
|
|
598
|
-
`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(checkoutSessionId)}`,
|
|
599
|
-
{ headers },
|
|
600
|
-
NETWORK_RETRY_ATTEMPTS,
|
|
601
|
-
(attempt) => {
|
|
602
|
-
this.telemetryHooks?.onRetry?.("session_read", attempt);
|
|
603
|
-
this.directTelemetry?.log({
|
|
604
|
-
name: "operation.retry",
|
|
605
|
-
stage: "session_read",
|
|
606
|
-
requestCategory: "session_read",
|
|
607
|
-
attempt
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
);
|
|
611
|
-
const firstByteDuration = Math.max(0, this.telemetryTimestamp() - requestStarted);
|
|
612
|
-
try {
|
|
613
|
-
this.telemetryHooks?.onFirstByte?.(firstByteDuration);
|
|
614
|
-
} catch {
|
|
615
|
-
}
|
|
616
|
-
const statusClass = `${Math.floor(response.status / 100)}xx`;
|
|
617
|
-
this.directTelemetry?.log({
|
|
618
|
-
name: "session.request.first_byte",
|
|
619
|
-
stage: "session_first_byte",
|
|
620
|
-
requestCategory: "session_read",
|
|
621
|
-
statusClass
|
|
622
|
-
});
|
|
623
|
-
this.directTelemetry?.performance({
|
|
624
|
-
stage: "session_first_byte",
|
|
625
|
-
durationMs: firstByteDuration,
|
|
626
|
-
durationMode: "machine",
|
|
627
|
-
requestCategory: "session_read",
|
|
628
|
-
statusClass
|
|
629
|
-
});
|
|
630
|
-
if (!response.ok) {
|
|
631
|
-
throw await buildApiErrorFromResponse(response, "Failed to get checkout session");
|
|
632
|
-
}
|
|
633
|
-
const body = await response.json();
|
|
634
|
-
this.directTelemetry?.log({
|
|
635
|
-
name: "session.request.completed",
|
|
636
|
-
stage: "session_complete",
|
|
637
|
-
requestCategory: "session_read",
|
|
638
|
-
statusClass
|
|
639
|
-
});
|
|
640
|
-
this.directTelemetry?.performance({
|
|
641
|
-
stage: "session_complete",
|
|
642
|
-
durationMs: Math.max(0, this.telemetryTimestamp() - requestStarted),
|
|
643
|
-
durationMode: "machine",
|
|
644
|
-
requestCategory: "session_read",
|
|
645
|
-
statusClass
|
|
646
|
-
});
|
|
647
|
-
return { ...body, data: this.mergeCachedDisplayData(body.data) };
|
|
648
|
-
} catch (error) {
|
|
649
|
-
const statusCode = error instanceof import_shared2.FloPayError ? error.statusCode : void 0;
|
|
650
|
-
this.directTelemetry?.error({
|
|
651
|
-
errorCode: error instanceof import_shared2.FloPayError && error.code === "checkout_processing_timeout" ? "REQUEST_TIMEOUT" : "NETWORK_REQUEST_FAILED",
|
|
652
|
-
stage: "session_read",
|
|
653
|
-
requestCategory: "session_read",
|
|
654
|
-
statusClass: statusCode ? `${Math.floor(statusCode / 100)}xx` : "network_error"
|
|
655
|
-
});
|
|
656
|
-
throw error;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Stash display-only data for a session so subsequent fetches can fill in
|
|
661
|
-
* fields the backend no longer persists (`overrideAmount`, `totalAmount`,
|
|
662
|
-
* `providerItemName`, `providerPlanName`).
|
|
663
|
-
*
|
|
664
|
-
* Backed by `sessionStorage` in the browser, with an in-memory fallback in
|
|
665
|
-
* Node/SSR contexts. Default TTL: 1 hour.
|
|
666
|
-
*
|
|
667
|
-
* Server-returned values always win — cached values fill in only where the
|
|
668
|
-
* server returned `null` / `undefined`.
|
|
669
|
-
*
|
|
670
|
-
* @example
|
|
671
|
-
* ```ts
|
|
672
|
-
* paymentAPI.cacheSessionDisplayData(sessionId, {
|
|
673
|
-
* currency: 'USD',
|
|
674
|
-
* items: [{ code: 'pro_plan', overrideAmount: 24.99, providerItemName: 'Pro' }],
|
|
675
|
-
* });
|
|
676
|
-
* ```
|
|
677
|
-
*/
|
|
678
|
-
cacheSessionDisplayData(sessionId, data, options) {
|
|
679
|
-
cacheSessionDisplayData(sessionId, data, options);
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Drop any cached display data for a session. Call after the payment
|
|
683
|
-
* completes; otherwise the TTL handles cleanup.
|
|
684
|
-
*/
|
|
685
|
-
clearSessionDisplayData(sessionId) {
|
|
686
|
-
clearSessionDisplayData(sessionId);
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Fetch (re-mint) the hosted vault capture widget for a session
|
|
690
|
-
* (TeamFloPay/backend#823).
|
|
691
|
-
*
|
|
692
|
-
* `POST /v1/checkouts/sessions/{id}/vault/capture` returns the SDK-ready
|
|
693
|
-
* {@link VaultCaptureBlock} (`html` + `url`, plus `messageToken` /
|
|
694
|
-
* `expectedOrigin` once the backend mints them). The SDK injects `html` as
|
|
695
|
-
* the card-capture widget. This is the fallback path for sessions that did
|
|
696
|
-
* not receive the embedded `vault` block on create (e.g. a session loaded by
|
|
697
|
-
* id via `GET`, or a pre-1.3.0 create); the endpoint is idempotent and reuses
|
|
698
|
-
* session-cached creds when available.
|
|
699
|
-
*
|
|
700
|
-
* Because the endpoint is idempotent, the request is wrapped in
|
|
701
|
-
* `fetchWithNetworkRetry`: a transient network blip (dropped connection, DNS
|
|
702
|
-
* hiccup, failed CORS preflight) would otherwise leave the secure card form
|
|
703
|
-
* unable to load and hard-block checkout.
|
|
704
|
-
*
|
|
705
|
-
* The PCIVault submit *secret* the backend may include in the response is
|
|
706
|
-
* intentionally **not** read or surfaced — it is server-only and never enters
|
|
707
|
-
* the SDK runtime.
|
|
708
|
-
*
|
|
709
|
-
* `nonce` is forwarded as `x-checkout-session-token` (required by post-#640
|
|
710
|
-
* backends, matched against the session's stored nonce).
|
|
711
|
-
*/
|
|
712
|
-
async getVaultCapture(checkoutSessionId, nonce) {
|
|
713
|
-
this.beginDirectTelemetryCheckout(checkoutSessionId);
|
|
714
|
-
const startedAt = this.telemetryTimestamp();
|
|
715
|
-
this.directTelemetry?.log({
|
|
716
|
-
name: "vault.capture.requested",
|
|
717
|
-
stage: "vault_request",
|
|
718
|
-
requestCategory: "vault_capture",
|
|
719
|
-
paymentMethodCategory: "card"
|
|
720
|
-
});
|
|
721
|
-
const headers = { "Content-Type": "application/json" };
|
|
722
|
-
if (nonce) headers["x-checkout-session-token"] = nonce;
|
|
723
|
-
try {
|
|
724
|
-
const response = await fetchWithNetworkRetry(
|
|
725
|
-
`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(checkoutSessionId)}/vault/capture`,
|
|
726
|
-
{ method: "POST", headers },
|
|
727
|
-
NETWORK_RETRY_ATTEMPTS,
|
|
728
|
-
(attempt) => {
|
|
729
|
-
this.telemetryHooks?.onRetry?.("vault_capture", attempt);
|
|
730
|
-
this.directTelemetry?.log({
|
|
731
|
-
name: "operation.retry",
|
|
732
|
-
stage: "vault_request",
|
|
733
|
-
requestCategory: "vault_capture",
|
|
734
|
-
paymentMethodCategory: "card",
|
|
735
|
-
attempt
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
);
|
|
739
|
-
if (!response.ok) {
|
|
740
|
-
throw await buildApiErrorFromResponse(response, "Failed to load the secure card form");
|
|
741
|
-
}
|
|
742
|
-
const block = await response.json();
|
|
743
|
-
this.directTelemetry?.performance({
|
|
744
|
-
stage: "vault_request",
|
|
745
|
-
durationMs: Math.max(0, this.telemetryTimestamp() - startedAt),
|
|
746
|
-
durationMode: "machine",
|
|
747
|
-
requestCategory: "vault_capture",
|
|
748
|
-
paymentMethodCategory: "card",
|
|
749
|
-
statusClass: "2xx"
|
|
750
|
-
});
|
|
751
|
-
return this.toVaultBlock(block);
|
|
752
|
-
} catch (error) {
|
|
753
|
-
this.reportDirectFailure(
|
|
754
|
-
error,
|
|
755
|
-
"VAULT_LOAD_FAILED",
|
|
756
|
-
"vault_request",
|
|
757
|
-
"vault_capture",
|
|
758
|
-
"card"
|
|
759
|
-
);
|
|
760
|
-
throw error;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* Fetch and normalize a checkout session.
|
|
765
|
-
*
|
|
766
|
-
* Reads the backend's `gateways` map to enumerate provider-specific data,
|
|
767
|
-
* then wraps the session in a `NormalizedCheckoutSession` for provider-
|
|
768
|
-
* agnostic consumption.
|
|
769
|
-
*/
|
|
770
|
-
async getUnifiedCheckoutSession(checkoutSessionId, nonce) {
|
|
771
|
-
const res = await this.getCheckoutSession(checkoutSessionId, nonce);
|
|
772
|
-
const normalized = this.normalizeRawSession(res.data);
|
|
773
|
-
const vault = res.vault;
|
|
774
|
-
if (vault && normalized.data.session) {
|
|
775
|
-
normalized.data.session.vault = this.toVaultBlock(vault);
|
|
776
|
-
}
|
|
777
|
-
return normalized;
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Submit a tokenized payment to the billing backend.
|
|
781
|
-
*
|
|
782
|
-
* The backend will either succeed, return `type: '3ds_required'`
|
|
783
|
-
* (with a `threeDSecureToken`), or return `type: 'paypal_redirect_required'`.
|
|
784
|
-
*
|
|
785
|
-
* Hits the session-scoped route `POST /v1/checkouts/sessions/:id/process`
|
|
786
|
-
* and forwards `data.nonce` as `x-checkout-session-token`. Backend
|
|
787
|
-
* `TeamFloPay/backend#640` rejects callers without a matching nonce with a
|
|
788
|
-
* 401 — this method throws synchronously when `data.nonce` is missing so the
|
|
789
|
-
* problem surfaces before the network round trip.
|
|
790
|
-
*
|
|
791
|
-
* @param userId Vestigial — backend's GatewayInterceptor routes via session,
|
|
792
|
-
* not headers, so this value is no longer sent on the wire. Kept in the
|
|
793
|
-
* signature for back-compat with existing callers; will be removed in a
|
|
794
|
-
* future major version.
|
|
795
|
-
*/
|
|
796
|
-
async processPayment(_userId, data, options) {
|
|
797
|
-
this.beginDirectTelemetryCheckout(data.sessionId);
|
|
798
|
-
if (!data.nonce) {
|
|
799
|
-
this.directTelemetry?.terminal({
|
|
800
|
-
outcome: "validation_rejected",
|
|
801
|
-
stage: "processing",
|
|
802
|
-
requestCategory: "process_payment"
|
|
803
|
-
});
|
|
804
|
-
throw new import_shared2.FloPayError(
|
|
805
|
-
"processPayment requires `nonce` \u2014 pass the value returned from session creation.",
|
|
806
|
-
"validation_error",
|
|
807
|
-
{ code: "MissingCheckoutSessionToken", param: "nonce" }
|
|
808
|
-
);
|
|
809
|
-
}
|
|
810
|
-
const startedAt = this.telemetryTimestamp();
|
|
811
|
-
this.directTelemetry?.log({
|
|
812
|
-
name: "payment.processing.started",
|
|
813
|
-
stage: "processing",
|
|
814
|
-
requestCategory: "process_payment"
|
|
815
|
-
});
|
|
816
|
-
const { nonce, ...processBody } = data;
|
|
817
|
-
let response;
|
|
818
|
-
try {
|
|
819
|
-
response = await fetch(
|
|
820
|
-
`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(data.sessionId)}/process`,
|
|
821
|
-
{
|
|
822
|
-
method: "POST",
|
|
823
|
-
headers: {
|
|
824
|
-
"Content-Type": "application/json",
|
|
825
|
-
"x-checkout-session-token": nonce
|
|
826
|
-
},
|
|
827
|
-
body: JSON.stringify(processBody)
|
|
828
|
-
}
|
|
829
|
-
);
|
|
830
|
-
} catch (error) {
|
|
831
|
-
this.reportDirectFailure(
|
|
832
|
-
error,
|
|
833
|
-
"PAYMENT_PROCESSING_FAILED",
|
|
834
|
-
"processing",
|
|
835
|
-
"process_payment"
|
|
836
|
-
);
|
|
837
|
-
throw error;
|
|
838
|
-
}
|
|
839
|
-
if (!response.ok && response.status !== 202) {
|
|
840
|
-
this.directTelemetry?.error({
|
|
841
|
-
errorCode: "PAYMENT_PROCESSING_FAILED",
|
|
842
|
-
stage: "processing",
|
|
843
|
-
requestCategory: "process_payment",
|
|
844
|
-
statusClass: telemetryStatusClass(response.status)
|
|
845
|
-
});
|
|
846
|
-
return response;
|
|
847
|
-
}
|
|
848
|
-
try {
|
|
849
|
-
const result = await this.resolveProcessResponse(
|
|
850
|
-
response,
|
|
851
|
-
data.sessionId,
|
|
852
|
-
{ ...options, nonce }
|
|
853
|
-
);
|
|
854
|
-
this.directTelemetry?.log({
|
|
855
|
-
name: "payment.processing.completed",
|
|
856
|
-
stage: "processing",
|
|
857
|
-
requestCategory: "process_payment",
|
|
858
|
-
statusClass: telemetryStatusClass(result.status)
|
|
859
|
-
});
|
|
860
|
-
this.directTelemetry?.performance({
|
|
861
|
-
stage: "processing",
|
|
862
|
-
durationMs: Math.max(0, this.telemetryTimestamp() - startedAt),
|
|
863
|
-
durationMode: "machine",
|
|
864
|
-
requestCategory: "process_payment",
|
|
865
|
-
statusClass: telemetryStatusClass(result.status)
|
|
866
|
-
});
|
|
867
|
-
return result;
|
|
868
|
-
} catch (error) {
|
|
869
|
-
if (!(error instanceof import_shared2.FloPayError && error.code === "checkout_processing_timeout")) {
|
|
870
|
-
this.reportDirectFailure(
|
|
871
|
-
error,
|
|
872
|
-
"PAYMENT_PROCESSING_FAILED",
|
|
873
|
-
"processing",
|
|
874
|
-
"process_payment"
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
throw error;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Patch the buyer's account snapshot (email, name, billing address, AVS
|
|
882
|
-
* intent) onto a checkout session via
|
|
883
|
-
* `PATCH /v1/checkouts/sessions/{id}/account` (TeamFloPay/backend#823).
|
|
884
|
-
*
|
|
885
|
-
* The vault path's hosted form owns the charge end-to-end so the SDK
|
|
886
|
-
* never calls `/process` on this path; the buyer-typed AVS / billing
|
|
887
|
-
* address would otherwise be lost. The SDK calls this just before
|
|
888
|
-
* submitting the vault widget so the downstream listener mints the
|
|
889
|
-
* Stripe PaymentMethod with the right `billing_details.address` and the
|
|
890
|
-
* per-attempt + per-PM address snapshots are populated.
|
|
891
|
-
*
|
|
892
|
-
* Body shape mirrors the relevant subset of `/process`'s
|
|
893
|
-
* `ProcessCheckoutBodyDto` — same keys, same validators. The endpoint
|
|
894
|
-
* is idempotent: empty/undefined fields are not written, addresses are
|
|
895
|
-
* last-writer-wins, AVS analytics are first-writer-wins.
|
|
896
|
-
*
|
|
897
|
-
* Wrapped in `fetchWithNetworkRetry` because a transient blip on this
|
|
898
|
-
* pre-pay PATCH would silently leave AVS unsent and cause an
|
|
899
|
-
* AVS-protected charge to decline downstream.
|
|
900
|
-
*/
|
|
901
|
-
async patchAccountSnapshot(sessionId, nonce, body, options) {
|
|
902
|
-
this.beginDirectTelemetryCheckout(sessionId);
|
|
903
|
-
const startedAt = this.telemetryTimestamp();
|
|
904
|
-
this.directTelemetry?.log({
|
|
905
|
-
name: "operation.state_transition",
|
|
906
|
-
stage: "processing",
|
|
907
|
-
requestCategory: "account_snapshot"
|
|
908
|
-
});
|
|
909
|
-
const timeoutMs = options?.timeoutMs ?? DEFAULT_ACCOUNT_SNAPSHOT_TIMEOUT_MS;
|
|
910
|
-
const controller = new AbortController();
|
|
911
|
-
const onCallerAbort = () => controller.abort();
|
|
912
|
-
if (options?.signal) {
|
|
913
|
-
if (options.signal.aborted) controller.abort();
|
|
914
|
-
else options.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
915
|
-
}
|
|
916
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
917
|
-
try {
|
|
918
|
-
let response;
|
|
919
|
-
try {
|
|
920
|
-
response = await fetchWithNetworkRetry(
|
|
921
|
-
`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(sessionId)}/account`,
|
|
922
|
-
{
|
|
923
|
-
method: "PATCH",
|
|
924
|
-
headers: {
|
|
925
|
-
"Content-Type": "application/json",
|
|
926
|
-
"x-checkout-session-token": nonce
|
|
927
|
-
},
|
|
928
|
-
body: JSON.stringify(body),
|
|
929
|
-
signal: controller.signal
|
|
930
|
-
},
|
|
931
|
-
NETWORK_RETRY_ATTEMPTS,
|
|
932
|
-
(attempt) => {
|
|
933
|
-
this.telemetryHooks?.onRetry?.("account_snapshot", attempt);
|
|
934
|
-
this.directTelemetry?.log({
|
|
935
|
-
name: "operation.retry",
|
|
936
|
-
stage: "processing",
|
|
937
|
-
requestCategory: "account_snapshot",
|
|
938
|
-
attempt
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
);
|
|
942
|
-
} finally {
|
|
943
|
-
clearTimeout(timer);
|
|
944
|
-
options?.signal?.removeEventListener("abort", onCallerAbort);
|
|
945
|
-
}
|
|
946
|
-
if (!response.ok) {
|
|
947
|
-
throw await buildApiErrorFromResponse(response, "Failed to persist account snapshot");
|
|
948
|
-
}
|
|
949
|
-
this.directTelemetry?.performance({
|
|
950
|
-
stage: "processing",
|
|
951
|
-
durationMs: Math.max(0, this.telemetryTimestamp() - startedAt),
|
|
952
|
-
durationMode: "machine",
|
|
953
|
-
requestCategory: "account_snapshot",
|
|
954
|
-
statusClass: "2xx"
|
|
955
|
-
});
|
|
956
|
-
} catch (error) {
|
|
957
|
-
this.reportDirectFailure(
|
|
958
|
-
error,
|
|
959
|
-
"NETWORK_REQUEST_FAILED",
|
|
960
|
-
"processing",
|
|
961
|
-
"account_snapshot"
|
|
962
|
-
);
|
|
963
|
-
throw error;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
/** Create a wallet/APM/PayPal intent through the session-scoped contract. */
|
|
967
|
-
async createSessionIntent(sessionId, nonce, request, options) {
|
|
968
|
-
if (!nonce) {
|
|
969
|
-
throw new import_shared2.FloPayError(
|
|
970
|
-
"createSessionIntent requires the checkout session nonce.",
|
|
971
|
-
"validation_error",
|
|
972
|
-
{ code: "MissingCheckoutSessionToken", param: "nonce" }
|
|
973
|
-
);
|
|
974
|
-
}
|
|
975
|
-
const rawRequest = request;
|
|
976
|
-
const paymentMethodType = rawRequest["paymentMethodType"];
|
|
977
|
-
const directCardType = typeof paymentMethodType === "string" && paymentMethodType.trim().toLowerCase() === "card";
|
|
978
|
-
const commonRequestFieldsValid = typeof paymentMethodType === "string" && paymentMethodType.length > 0 && !directCardType && (typeof rawRequest["paymentMethodId"] === "string" || rawRequest["paymentMethodId"] === null);
|
|
979
|
-
const stripeRequestValid = rawRequest["provider"] === "stripe" && (rawRequest["paymentMethodCategory"] === "wallet" || rawRequest["paymentMethodCategory"] === "apm") && (rawRequest["intentKind"] === "payment" || rawRequest["intentKind"] === "setup");
|
|
980
|
-
const paypalRequestValid = rawRequest["provider"] === "paypal" && rawRequest["paymentMethodCategory"] === "wallet" && rawRequest["paymentMethodType"] === "paypal" && rawRequest["paymentMethodId"] === null && rawRequest["intentKind"] === "payment";
|
|
981
|
-
if (!commonRequestFieldsValid || !stripeRequestValid && !paypalRequestValid) {
|
|
982
|
-
throw new import_shared2.FloPayError(
|
|
983
|
-
"Only wallet, APM, and PayPal session intents are supported.",
|
|
984
|
-
"validation_error",
|
|
985
|
-
{ code: "InvalidSessionIntentRequest" }
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
const suppliedAttemptId = rawRequest["authorizationAttemptId"];
|
|
989
|
-
if (suppliedAttemptId !== void 0 && !(0, import_shared2.isUuidV4)(suppliedAttemptId)) {
|
|
990
|
-
throw new import_shared2.FloPayError(
|
|
991
|
-
"authorizationAttemptId must be a v4 UUID identifying one buyer authorization attempt.",
|
|
992
|
-
"validation_error",
|
|
993
|
-
{ code: "InvalidAuthorizationAttemptId", param: "authorizationAttemptId" }
|
|
994
|
-
);
|
|
995
|
-
}
|
|
996
|
-
const authorizationAttemptId = (0, import_shared2.isUuidV4)(suppliedAttemptId) ? suppliedAttemptId : (0, import_shared2.randomUuidV4)();
|
|
997
|
-
this.beginDirectTelemetryCheckout(sessionId);
|
|
998
|
-
const startedAt = this.telemetryTimestamp();
|
|
999
|
-
this.directTelemetry?.log({
|
|
1000
|
-
name: "payment.intent.started",
|
|
1001
|
-
stage: "processing",
|
|
1002
|
-
requestCategory: "intent_create"
|
|
1003
|
-
});
|
|
1004
|
-
const headers = {
|
|
1005
|
-
"Content-Type": "application/json",
|
|
1006
|
-
"x-checkout-session-token": nonce,
|
|
1007
|
-
[import_shared2.IDEMPOTENCY_KEY_HEADER]: options?.idempotencyKey || authorizationAttemptId
|
|
1008
|
-
};
|
|
1009
|
-
let failureReported = false;
|
|
1010
|
-
try {
|
|
1011
|
-
const response = await fetch(
|
|
1012
|
-
`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(sessionId)}/intents`,
|
|
1013
|
-
{
|
|
1014
|
-
method: "POST",
|
|
1015
|
-
headers,
|
|
1016
|
-
body: JSON.stringify({ ...request, authorizationAttemptId }),
|
|
1017
|
-
signal: options?.signal
|
|
1018
|
-
}
|
|
1019
|
-
);
|
|
1020
|
-
if (!response.ok) {
|
|
1021
|
-
failureReported = true;
|
|
1022
|
-
this.directTelemetry?.error({
|
|
1023
|
-
errorCode: "PAYMENT_PROCESSING_FAILED",
|
|
1024
|
-
stage: "processing",
|
|
1025
|
-
requestCategory: "intent_create",
|
|
1026
|
-
statusClass: telemetryStatusClass(response.status)
|
|
1027
|
-
});
|
|
1028
|
-
throw await buildApiErrorFromResponse(response, "Failed to create checkout intent");
|
|
1029
|
-
}
|
|
1030
|
-
const body = await response.json();
|
|
1031
|
-
const data = body.data;
|
|
1032
|
-
if (!data || typeof data !== "object") {
|
|
1033
|
-
throw new import_shared2.FloPayError("Invalid checkout intent response.", "api_error", {
|
|
1034
|
-
code: "InvalidSessionIntentResponse"
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
const intent = data;
|
|
1038
|
-
const responsePaymentMethodType = intent["paymentMethodType"];
|
|
1039
|
-
const commonFieldsValid = (intent["paymentMethodCategory"] === "wallet" || intent["paymentMethodCategory"] === "apm") && typeof responsePaymentMethodType === "string" && responsePaymentMethodType.trim().toLowerCase() !== "card" && (typeof intent["paymentMethodId"] === "string" || intent["paymentMethodId"] === null) && typeof intent["providerObjectId"] === "string";
|
|
1040
|
-
const stripeValid = intent["provider"] === "stripe" && (intent["intentKind"] === "payment" || intent["intentKind"] === "setup") && typeof intent["clientSecret"] === "string";
|
|
1041
|
-
const paypalValid = intent["provider"] === "paypal" && intent["paymentMethodCategory"] === "wallet" && intent["paymentMethodType"] === "paypal" && intent["paymentMethodId"] === null && intent["intentKind"] === "payment" && (intent["providerObjectType"] === "order" || intent["providerObjectType"] === "subscription") && intent["clientSecret"] === null;
|
|
1042
|
-
const discriminantsMatchRequest = intent["provider"] === request.provider && intent["paymentMethodCategory"] === request.paymentMethodCategory && intent["paymentMethodType"] === request.paymentMethodType && intent["paymentMethodId"] === request.paymentMethodId && intent["intentKind"] === request.intentKind;
|
|
1043
|
-
if (!commonFieldsValid || !stripeValid && !paypalValid || !discriminantsMatchRequest) {
|
|
1044
|
-
throw new import_shared2.FloPayError("Invalid checkout intent response.", "api_error", {
|
|
1045
|
-
code: "InvalidSessionIntentResponse"
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
this.directTelemetry?.log({
|
|
1049
|
-
name: "payment.intent.completed",
|
|
1050
|
-
stage: "processing",
|
|
1051
|
-
requestCategory: "intent_create",
|
|
1052
|
-
statusClass: telemetryStatusClass(response.status)
|
|
1053
|
-
});
|
|
1054
|
-
this.directTelemetry?.performance({
|
|
1055
|
-
stage: "processing",
|
|
1056
|
-
durationMs: Math.max(0, this.telemetryTimestamp() - startedAt),
|
|
1057
|
-
durationMode: "machine",
|
|
1058
|
-
requestCategory: "intent_create",
|
|
1059
|
-
statusClass: telemetryStatusClass(response.status)
|
|
1060
|
-
});
|
|
1061
|
-
return intent;
|
|
1062
|
-
} catch (error) {
|
|
1063
|
-
if (!failureReported) {
|
|
1064
|
-
this.reportDirectFailure(
|
|
1065
|
-
error,
|
|
1066
|
-
"PAYMENT_PROCESSING_FAILED",
|
|
1067
|
-
"processing",
|
|
1068
|
-
"intent_create"
|
|
1069
|
-
);
|
|
1070
|
-
}
|
|
1071
|
-
throw error;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
/** Record a provider-neutral non-card decline without sensitive identifiers. */
|
|
1075
|
-
async reportSessionIntentDecline(sessionId, nonce, request, options) {
|
|
1076
|
-
if (!nonce) {
|
|
1077
|
-
throw new import_shared2.FloPayError(
|
|
1078
|
-
"reportSessionIntentDecline requires the checkout session nonce.",
|
|
1079
|
-
"validation_error",
|
|
1080
|
-
{ code: "MissingCheckoutSessionToken", param: "nonce" }
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
const rawRequest = request;
|
|
1084
|
-
const reason = rawRequest["providerDeclineReason"];
|
|
1085
|
-
const paymentMethodType = rawRequest["paymentMethodType"];
|
|
1086
|
-
const safeReason = typeof reason === "string" && /^[a-z0-9][a-z0-9_.:-]{0,63}$/i.test(reason) && !/^(?:pm|pi|seti|tok|src|cus|sess|sk|pk)_/i.test(reason);
|
|
1087
|
-
const commonFieldsValid = typeof paymentMethodType === "string" && paymentMethodType.length > 0 && paymentMethodType.trim().toLowerCase() !== "card" && safeReason;
|
|
1088
|
-
const stripeFieldsValid = rawRequest["provider"] === "stripe" && (rawRequest["paymentMethodCategory"] === "wallet" || rawRequest["paymentMethodCategory"] === "apm");
|
|
1089
|
-
const paypalFieldsValid = rawRequest["provider"] === "paypal" && rawRequest["paymentMethodCategory"] === "wallet" && rawRequest["paymentMethodType"] === "paypal";
|
|
1090
|
-
if (!commonFieldsValid || !stripeFieldsValid && !paypalFieldsValid) {
|
|
1091
|
-
throw new import_shared2.FloPayError(
|
|
1092
|
-
"Invalid non-card decline classification.",
|
|
1093
|
-
"validation_error",
|
|
1094
|
-
{ code: "InvalidSessionIntentDeclineRequest" }
|
|
1095
|
-
);
|
|
1096
|
-
}
|
|
1097
|
-
const safeRequest = paypalFieldsValid ? {
|
|
1098
|
-
provider: "paypal",
|
|
1099
|
-
paymentMethodCategory: "wallet",
|
|
1100
|
-
paymentMethodType: "paypal",
|
|
1101
|
-
providerDeclineReason: reason
|
|
1102
|
-
} : {
|
|
1103
|
-
provider: "stripe",
|
|
1104
|
-
paymentMethodCategory: rawRequest["paymentMethodCategory"],
|
|
1105
|
-
paymentMethodType: rawRequest["paymentMethodType"],
|
|
1106
|
-
providerDeclineReason: reason
|
|
1107
|
-
};
|
|
1108
|
-
const response = await fetch(
|
|
1109
|
-
`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(sessionId)}/intents/decline`,
|
|
1110
|
-
{
|
|
1111
|
-
method: "POST",
|
|
1112
|
-
headers: {
|
|
1113
|
-
"Content-Type": "application/json",
|
|
1114
|
-
"x-checkout-session-token": nonce
|
|
1115
|
-
},
|
|
1116
|
-
body: JSON.stringify(safeRequest),
|
|
1117
|
-
signal: options?.signal
|
|
1118
|
-
}
|
|
1119
|
-
);
|
|
1120
|
-
if (!response.ok) {
|
|
1121
|
-
throw await buildApiErrorFromResponse(response, "Failed to report checkout decline");
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Fetch user's prior payments by email.
|
|
1126
|
-
* Used to determine if saved card UX should be shown.
|
|
1127
|
-
*/
|
|
1128
|
-
async getPaymentsByEmail(email, options) {
|
|
1129
|
-
this.beginDirectTelemetryOperation();
|
|
1130
|
-
const startedAt = this.telemetryTimestamp();
|
|
1131
|
-
this.directTelemetry?.log({
|
|
1132
|
-
name: "operation.recovery.started",
|
|
1133
|
-
stage: "recovery",
|
|
1134
|
-
requestCategory: "other",
|
|
1135
|
-
paymentMethodCategory: "saved"
|
|
1136
|
-
});
|
|
1137
|
-
const page = options?.page ?? 1;
|
|
1138
|
-
const limit = options?.limit ?? 1;
|
|
1139
|
-
const params = new URLSearchParams({
|
|
1140
|
-
email,
|
|
1141
|
-
page: String(page),
|
|
1142
|
-
limit: String(limit),
|
|
1143
|
-
sortField: "createdAt",
|
|
1144
|
-
sortDirection: "DESC"
|
|
1145
|
-
});
|
|
1146
|
-
try {
|
|
1147
|
-
const response = await fetch(
|
|
1148
|
-
`${this.baseUrl}/v1/payments?${params.toString()}`,
|
|
1149
|
-
{
|
|
1150
|
-
method: "GET",
|
|
1151
|
-
signal: options?.signal,
|
|
1152
|
-
keepalive: true
|
|
1153
|
-
}
|
|
1154
|
-
);
|
|
1155
|
-
if (!response.ok) {
|
|
1156
|
-
throw new import_shared2.FloPayError(
|
|
1157
|
-
"Failed to fetch payments",
|
|
1158
|
-
"api_error",
|
|
1159
|
-
{ statusCode: response.status }
|
|
1160
|
-
);
|
|
1161
|
-
}
|
|
1162
|
-
const result = await response.json();
|
|
1163
|
-
this.directTelemetry?.log({
|
|
1164
|
-
name: "operation.recovery.completed",
|
|
1165
|
-
stage: "recovery",
|
|
1166
|
-
requestCategory: "other",
|
|
1167
|
-
paymentMethodCategory: "saved",
|
|
1168
|
-
statusClass: "2xx"
|
|
1169
|
-
});
|
|
1170
|
-
this.directTelemetry?.performance({
|
|
1171
|
-
stage: "recovery",
|
|
1172
|
-
durationMs: Math.max(0, this.telemetryTimestamp() - startedAt),
|
|
1173
|
-
durationMode: "machine",
|
|
1174
|
-
requestCategory: "other",
|
|
1175
|
-
paymentMethodCategory: "saved",
|
|
1176
|
-
statusClass: "2xx"
|
|
1177
|
-
});
|
|
1178
|
-
return result;
|
|
1179
|
-
} catch (error) {
|
|
1180
|
-
this.reportDirectFailure(error, "RECOVERY_FAILED", "recovery", "other", "saved");
|
|
1181
|
-
throw error;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
/**
|
|
1185
|
-
* Create a checkout session AND return the full session data in one call.
|
|
1186
|
-
* Uses `?expand=true` so the backend returns the complete session
|
|
1187
|
-
* instead of just a UUID — eliminating the need for a second GET.
|
|
1188
|
-
*
|
|
1189
|
-
* Falls back to create + GET if the backend doesn't support `expand`.
|
|
1190
|
-
*/
|
|
1191
|
-
async createAndFetchSession(params) {
|
|
1192
|
-
this.beginDirectTelemetryOperation();
|
|
1193
|
-
const startedAt = this.telemetryTimestamp();
|
|
1194
|
-
this.directTelemetry?.log({
|
|
1195
|
-
name: "session.create.started",
|
|
1196
|
-
stage: "session_create",
|
|
1197
|
-
requestCategory: "session_create"
|
|
1198
|
-
});
|
|
1199
|
-
try {
|
|
1200
|
-
const result = await this.createAndFetchSessionRequest(params, startedAt);
|
|
1201
|
-
this.adoptDirectTelemetryCheckout(result.data.session?.id);
|
|
1202
|
-
this.directTelemetry?.log({
|
|
1203
|
-
name: "session.request.completed",
|
|
1204
|
-
stage: "session_complete",
|
|
1205
|
-
requestCategory: "session_create",
|
|
1206
|
-
statusClass: "2xx"
|
|
1207
|
-
});
|
|
1208
|
-
this.directTelemetry?.performance({
|
|
1209
|
-
stage: "session_create",
|
|
1210
|
-
durationMs: this.telemetryTimestamp() - startedAt,
|
|
1211
|
-
durationMode: "machine",
|
|
1212
|
-
requestCategory: "session_create",
|
|
1213
|
-
statusClass: "2xx"
|
|
1214
|
-
});
|
|
1215
|
-
return result;
|
|
1216
|
-
} catch (error) {
|
|
1217
|
-
if (!(error instanceof import_shared2.FloPayError && error.code === "session_auto_completed")) {
|
|
1218
|
-
try {
|
|
1219
|
-
this.telemetryHooks?.onSessionCreateFailure?.(error);
|
|
1220
|
-
} catch {
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (error instanceof import_shared2.FloPayError && error.type === "validation_error") {
|
|
1224
|
-
this.directTelemetry?.terminal({
|
|
1225
|
-
outcome: "validation_rejected",
|
|
1226
|
-
stage: "session_create",
|
|
1227
|
-
requestCategory: "session_create"
|
|
1228
|
-
});
|
|
1229
|
-
} else if (!(error instanceof import_shared2.FloPayError && error.code === "session_auto_completed")) {
|
|
1230
|
-
const statusCode = error instanceof import_shared2.FloPayError ? error.statusCode : void 0;
|
|
1231
|
-
this.directTelemetry?.error({
|
|
1232
|
-
errorCode: error instanceof Error && error.name === "AbortError" ? "REQUEST_TIMEOUT" : error instanceof TypeError ? "NETWORK_REQUEST_FAILED" : "CHECKOUT_SESSION_CREATE_FAILED",
|
|
1233
|
-
stage: "session_create",
|
|
1234
|
-
requestCategory: "session_create",
|
|
1235
|
-
statusClass: error instanceof Error && error.name === "AbortError" ? "timeout" : statusCode ? `${Math.floor(statusCode / 100)}xx` : "network_error"
|
|
1236
|
-
});
|
|
1237
|
-
}
|
|
1238
|
-
throw error;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
async createAndFetchSessionRequest(params, telemetryStartedAt) {
|
|
1242
|
-
const wireProducts = params.products ?? (0, import_shared2.foldIntoProducts)(params.items, params.subscriptions);
|
|
1243
|
-
const sessionCurrency = (0, import_shared2.resolveSessionCurrency)(
|
|
1244
|
-
params.currency,
|
|
1245
|
-
params.items,
|
|
1246
|
-
params.subscriptions,
|
|
1247
|
-
wireProducts
|
|
1248
|
-
);
|
|
1249
|
-
if (!sessionCurrency) {
|
|
1250
|
-
throw new import_shared2.FloPayError(
|
|
1251
|
-
"currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.",
|
|
1252
|
-
"validation_error",
|
|
1253
|
-
{ code: "CurrencyRequired", param: "currency" }
|
|
1254
|
-
);
|
|
1255
|
-
}
|
|
1256
|
-
const payload = {
|
|
1257
|
-
clientId: params.clientId,
|
|
1258
|
-
checkoutVersion: import_shared2.SDK_VERSION,
|
|
1259
|
-
successUrl: params.successUrl,
|
|
1260
|
-
cancelUrl: params.cancelUrl,
|
|
1261
|
-
currency: sessionCurrency,
|
|
1262
|
-
checkoutMode: params.checkoutMode ?? "full",
|
|
1263
|
-
products: wireProducts.map((product) => (0, import_shared2.buildProductPayload)(product, sessionCurrency)),
|
|
1264
|
-
accountData: {
|
|
1265
|
-
userId: params.account.userId,
|
|
1266
|
-
firstName: params.account.firstName ?? null,
|
|
1267
|
-
lastName: params.account.lastName ?? null,
|
|
1268
|
-
email: params.account.email,
|
|
1269
|
-
country: params.account.country ?? null,
|
|
1270
|
-
gender: params.account.gender ?? null,
|
|
1271
|
-
city: params.account.city ?? null,
|
|
1272
|
-
state: params.account.state ?? null,
|
|
1273
|
-
zip: params.account.zip ?? null,
|
|
1274
|
-
addressLine1: params.account.addressLine1 ?? null,
|
|
1275
|
-
addressLine2: params.account.addressLine2 ?? null
|
|
1276
|
-
},
|
|
1277
|
-
couponCodes: params.couponCodes ?? []
|
|
1278
|
-
};
|
|
1279
|
-
if (params.tokenizedData) payload["tokenizedData"] = params.tokenizedData;
|
|
1280
|
-
if (params.tagsData) payload["tagsData"] = params.tagsData;
|
|
1281
|
-
if (params.utmMetadata?.length) payload["utmMetadata"] = params.utmMetadata;
|
|
1282
|
-
if (params.avsCheck !== void 0) payload["avsCheck"] = params.avsCheck;
|
|
1283
|
-
if (params.checkoutType) payload["checkoutType"] = params.checkoutType;
|
|
1284
|
-
if (params.checkoutLayout) payload["checkoutLayout"] = params.checkoutLayout;
|
|
1285
|
-
if (params.avsConfig) payload["avsConfig"] = params.avsConfig;
|
|
1286
|
-
const headers = {
|
|
1287
|
-
"Content-Type": "application/json",
|
|
1288
|
-
// Retain the SDK version header for compatibility visibility. Backends
|
|
1289
|
-
// must not gate the hosted vault block on this value.
|
|
1290
|
-
[import_shared2.FLO_SDK_VERSION_HEADER]: import_shared2.SDK_VERSION
|
|
1291
|
-
};
|
|
1292
|
-
const idempotencyKey = (0, import_shared2.resolveIdempotencyKey)(params.idempotencyKey);
|
|
1293
|
-
if (idempotencyKey) {
|
|
1294
|
-
headers[import_shared2.IDEMPOTENCY_KEY_HEADER] = idempotencyKey;
|
|
1295
|
-
}
|
|
1296
|
-
let response;
|
|
1297
|
-
let firstByteReported = false;
|
|
1298
|
-
for (let attempt = 0; ; attempt++) {
|
|
1299
|
-
response = await fetchWithNetworkRetry(
|
|
1300
|
-
`${this.baseUrl}/v1/checkouts/sessions?expand=true`,
|
|
1301
|
-
{
|
|
1302
|
-
method: "POST",
|
|
1303
|
-
headers,
|
|
1304
|
-
body: JSON.stringify(payload)
|
|
1305
|
-
},
|
|
1306
|
-
NETWORK_RETRY_ATTEMPTS,
|
|
1307
|
-
(networkAttempt) => {
|
|
1308
|
-
this.telemetryHooks?.onRetry?.("session_create", networkAttempt);
|
|
1309
|
-
this.directTelemetry?.log({
|
|
1310
|
-
name: "operation.retry",
|
|
1311
|
-
stage: "session_create",
|
|
1312
|
-
requestCategory: "session_create",
|
|
1313
|
-
attempt: networkAttempt
|
|
1314
|
-
});
|
|
1315
|
-
}
|
|
1316
|
-
);
|
|
1317
|
-
if (!firstByteReported) {
|
|
1318
|
-
firstByteReported = true;
|
|
1319
|
-
const statusClass = `${Math.floor(response.status / 100)}xx`;
|
|
1320
|
-
this.directTelemetry?.log({
|
|
1321
|
-
name: "session.request.first_byte",
|
|
1322
|
-
stage: "session_first_byte",
|
|
1323
|
-
requestCategory: "session_create",
|
|
1324
|
-
statusClass
|
|
1325
|
-
});
|
|
1326
|
-
this.directTelemetry?.performance({
|
|
1327
|
-
stage: "session_first_byte",
|
|
1328
|
-
durationMs: this.telemetryTimestamp() - telemetryStartedAt,
|
|
1329
|
-
durationMode: "machine",
|
|
1330
|
-
requestCategory: "session_create",
|
|
1331
|
-
statusClass
|
|
1332
|
-
});
|
|
1333
|
-
}
|
|
1334
|
-
if (response.status === 204) {
|
|
1335
|
-
throw new import_shared2.FloPayError(
|
|
1336
|
-
"Session auto-completed \u2014 payment method already on file",
|
|
1337
|
-
"api_error",
|
|
1338
|
-
{ code: "session_auto_completed" }
|
|
1339
|
-
);
|
|
1340
|
-
}
|
|
1341
|
-
if (response.ok) break;
|
|
1342
|
-
const error = await buildApiErrorFromResponse(response, "Failed to create checkout session");
|
|
1343
|
-
if (error.code === import_shared2.IDEMPOTENCY_IN_PROGRESS_CODE && attempt < IDEMPOTENCY_IN_PROGRESS_RETRY_ATTEMPTS) {
|
|
1344
|
-
try {
|
|
1345
|
-
this.telemetryHooks?.onRetry?.("session_create", attempt + 1);
|
|
1346
|
-
this.directTelemetry?.log({
|
|
1347
|
-
name: "operation.retry",
|
|
1348
|
-
stage: "session_create",
|
|
1349
|
-
requestCategory: "session_create",
|
|
1350
|
-
attempt: attempt + 1
|
|
1351
|
-
});
|
|
1352
|
-
} catch {
|
|
1353
|
-
}
|
|
1354
|
-
await delay(150 * 2 ** attempt);
|
|
1355
|
-
continue;
|
|
1356
|
-
}
|
|
1357
|
-
throw error;
|
|
1358
|
-
}
|
|
1359
|
-
const body = await response.json();
|
|
1360
|
-
if (body.data && "gateways" in body.data) {
|
|
1361
|
-
this.autoCacheDisplayData(body.data.uuid, params);
|
|
1362
|
-
const merged = this.mergeCachedDisplayData(body.data);
|
|
1363
|
-
const normalized = this.normalizeRawSession(merged);
|
|
1364
|
-
if (body.vault && normalized.data.session) {
|
|
1365
|
-
normalized.data.session.vault = this.toVaultBlock(body.vault);
|
|
1366
|
-
}
|
|
1367
|
-
return {
|
|
1368
|
-
...normalized,
|
|
1369
|
-
autoProcessingError: body.autoProcessingError,
|
|
1370
|
-
autoProcessingAttempted: body.autoProcessingAttempted,
|
|
1371
|
-
autoProcessingPending: body.autoProcessingPending
|
|
1372
|
-
};
|
|
1373
|
-
}
|
|
1374
|
-
const uuid = body.data?.uuid;
|
|
1375
|
-
if (!uuid) {
|
|
1376
|
-
throw new import_shared2.FloPayError("No session ID returned", "api_error");
|
|
1377
|
-
}
|
|
1378
|
-
this.autoCacheDisplayData(uuid, params);
|
|
1379
|
-
this.adoptDirectTelemetryCheckout(uuid);
|
|
1380
|
-
const unifiedSession = await this.getUnifiedCheckoutSession(uuid);
|
|
1381
|
-
return {
|
|
1382
|
-
...unifiedSession,
|
|
1383
|
-
autoProcessingError: body.autoProcessingError,
|
|
1384
|
-
autoProcessingAttempted: body.autoProcessingAttempted,
|
|
1385
|
-
autoProcessingPending: body.autoProcessingPending
|
|
1386
|
-
};
|
|
1387
|
-
}
|
|
1388
|
-
async waitForCheckoutSessionCompletion(checkoutSessionId, options) {
|
|
1389
|
-
this.beginDirectTelemetryCheckout(checkoutSessionId);
|
|
1390
|
-
const startedAt = this.telemetryTimestamp();
|
|
1391
|
-
this.directTelemetry?.log({
|
|
1392
|
-
name: "operation.recovery.started",
|
|
1393
|
-
stage: "recovery",
|
|
1394
|
-
requestCategory: "session_read",
|
|
1395
|
-
paymentMethodCategory: "saved"
|
|
1396
|
-
});
|
|
1397
|
-
const timeoutMs = options?.timeoutMs ?? DEFAULT_PROCESSING_TIMEOUT_MS;
|
|
1398
|
-
const deadline = Date.now() + timeoutMs;
|
|
1399
|
-
let nextDelayMs = this.clampRetryAfterMs(options?.initialDelayMs ?? DEFAULT_PROCESSING_RETRY_AFTER_MS);
|
|
1400
|
-
let pollAttempt = 0;
|
|
1401
|
-
try {
|
|
1402
|
-
while (true) {
|
|
1403
|
-
const remainingMs = deadline - Date.now();
|
|
1404
|
-
if (remainingMs <= 0) {
|
|
1405
|
-
throw createCheckoutProcessingTimeoutError();
|
|
1406
|
-
}
|
|
1407
|
-
if (nextDelayMs > 0) {
|
|
1408
|
-
try {
|
|
1409
|
-
pollAttempt += 1;
|
|
1410
|
-
this.telemetryHooks?.onRetry?.("session_read", pollAttempt);
|
|
1411
|
-
this.directTelemetry?.log({
|
|
1412
|
-
name: "operation.retry",
|
|
1413
|
-
stage: "recovery",
|
|
1414
|
-
requestCategory: "session_read",
|
|
1415
|
-
paymentMethodCategory: "saved",
|
|
1416
|
-
attempt: pollAttempt
|
|
1417
|
-
});
|
|
1418
|
-
} catch {
|
|
1419
|
-
}
|
|
1420
|
-
await delay(Math.min(nextDelayMs, remainingMs));
|
|
1421
|
-
if (Date.now() >= deadline) {
|
|
1422
|
-
throw createCheckoutProcessingTimeoutError();
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
const session = await this.getUnifiedCheckoutSession(checkoutSessionId, options?.nonce);
|
|
1426
|
-
const status = session.data.session?.status;
|
|
1427
|
-
if (status === "complete" || status === "expired") {
|
|
1428
|
-
this.directTelemetry?.log({
|
|
1429
|
-
name: "operation.recovery.completed",
|
|
1430
|
-
stage: "recovery",
|
|
1431
|
-
requestCategory: "session_read",
|
|
1432
|
-
paymentMethodCategory: "saved"
|
|
1433
|
-
});
|
|
1434
|
-
this.directTelemetry?.performance({
|
|
1435
|
-
stage: "recovery",
|
|
1436
|
-
durationMs: Math.max(0, this.telemetryTimestamp() - startedAt),
|
|
1437
|
-
durationMode: "machine",
|
|
1438
|
-
requestCategory: "session_read",
|
|
1439
|
-
paymentMethodCategory: "saved"
|
|
1440
|
-
});
|
|
1441
|
-
return session;
|
|
1442
|
-
}
|
|
1443
|
-
if (Date.now() >= deadline) {
|
|
1444
|
-
throw createCheckoutProcessingTimeoutError();
|
|
1445
|
-
}
|
|
1446
|
-
nextDelayMs = this.clampRetryAfterMs(
|
|
1447
|
-
Math.max(nextDelayMs * 2, MIN_PROCESSING_RETRY_AFTER_MS)
|
|
1448
|
-
);
|
|
1449
|
-
}
|
|
1450
|
-
} catch (error) {
|
|
1451
|
-
if (error instanceof import_shared2.FloPayError && error.code === "checkout_processing_timeout") {
|
|
1452
|
-
this.reportDirectFailure(
|
|
1453
|
-
error,
|
|
1454
|
-
"RECOVERY_FAILED",
|
|
1455
|
-
"recovery",
|
|
1456
|
-
"session_read",
|
|
1457
|
-
"saved"
|
|
1458
|
-
);
|
|
1459
|
-
}
|
|
1460
|
-
throw error;
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
/** Normalize a raw session into a provider-agnostic shape. */
|
|
1464
|
-
normalizeRawSession(session) {
|
|
1465
|
-
const gateways = session.gateways ?? {};
|
|
1466
|
-
const providers = [];
|
|
1467
|
-
const data = {
|
|
1468
|
-
session: this.toCheckoutSession(session)
|
|
1469
|
-
};
|
|
1470
|
-
const stripeGateway = gateways.stripe;
|
|
1471
|
-
if (stripeGateway?.publishableKey) {
|
|
1472
|
-
providers.push("stripe");
|
|
1473
|
-
const rawSession = session;
|
|
1474
|
-
const stripeClientSecret = [
|
|
1475
|
-
rawSession["stripeClientSecret"],
|
|
1476
|
-
stripeGateway.stripeClientSecret
|
|
1477
|
-
].find((value) => typeof value === "string" && value.length > 0);
|
|
1478
|
-
data.stripe = {
|
|
1479
|
-
clientSecret: stripeClientSecret ?? "",
|
|
1480
|
-
publishableKey: stripeGateway.publishableKey ?? void 0,
|
|
1481
|
-
paypalPublishableKey: stripeGateway.paypalPublishableKey ?? void 0,
|
|
1482
|
-
environment: stripeGateway.environment,
|
|
1483
|
-
enabledPaymentMethods: Array.isArray(stripeGateway.enabledPaymentMethods) ? stripeGateway.enabledPaymentMethods.filter((m) => typeof m === "string") : void 0
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
const paypalGateway = gateways.paypal;
|
|
1487
|
-
if (paypalGateway?.publishableKey) {
|
|
1488
|
-
providers.push("paypal");
|
|
1489
|
-
data.paypal = {
|
|
1490
|
-
publishableKey: paypalGateway.publishableKey,
|
|
1491
|
-
environment: paypalGateway.environment
|
|
1492
|
-
};
|
|
1493
|
-
}
|
|
1494
|
-
return {
|
|
1495
|
-
providers,
|
|
1496
|
-
mode: "tokenize",
|
|
1497
|
-
data,
|
|
1498
|
-
raw: { data: session }
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
/** Convert raw session to the SDK CheckoutSession shape. */
|
|
1502
|
-
toCheckoutSession(raw) {
|
|
1503
|
-
const rawProducts = raw.products ?? [];
|
|
1504
|
-
const hasBackendTotal = typeof raw.totalAmount === "number" && Number.isFinite(raw.totalAmount);
|
|
1505
|
-
const computedTotal = rawProducts.reduce(
|
|
1506
|
-
(sum, p) => sum + (p.overrideAmount ?? p.totalAmount ?? 0),
|
|
1507
|
-
0
|
|
1508
|
-
);
|
|
1509
|
-
const totalAmount = hasBackendTotal ? raw.totalAmount : computedTotal;
|
|
1510
|
-
const amountInCents = Math.round(totalAmount * 100);
|
|
1511
|
-
const currency = raw.currency ?? rawProducts[0]?.currency ?? "USD";
|
|
1512
|
-
const mode = rawProducts.some((p) => p.type === "subscription") ? "subscription" : "payment";
|
|
1513
|
-
return {
|
|
1514
|
-
id: raw.uuid,
|
|
1515
|
-
clientSecret: raw.nonce,
|
|
1516
|
-
mode,
|
|
1517
|
-
status: this.toCheckoutSessionStatus(raw.status),
|
|
1518
|
-
amount: amountInCents,
|
|
1519
|
-
currency,
|
|
1520
|
-
customer: {
|
|
1521
|
-
id: raw.accountData.userId,
|
|
1522
|
-
email: raw.accountData.email,
|
|
1523
|
-
firstName: raw.accountData.firstName,
|
|
1524
|
-
lastName: raw.accountData.lastName,
|
|
1525
|
-
country: raw.accountData.country ?? void 0,
|
|
1526
|
-
city: raw.accountData.city ?? void 0,
|
|
1527
|
-
state: raw.accountData.state ?? void 0,
|
|
1528
|
-
zip: raw.accountData.zip ?? void 0,
|
|
1529
|
-
gender: raw.accountData.gender ?? void 0,
|
|
1530
|
-
line1: raw.accountData.addressLine1 ?? void 0,
|
|
1531
|
-
line2: raw.accountData.addressLine2 ?? void 0
|
|
1532
|
-
},
|
|
1533
|
-
metadata: {},
|
|
1534
|
-
checkoutMode: raw.checkoutMode,
|
|
1535
|
-
providerPaymentMethodId: typeof raw.providerPaymentMethodId === "string" ? raw.providerPaymentMethodId : null,
|
|
1536
|
-
products: rawProducts.map((p) => ({
|
|
1537
|
-
...p,
|
|
1538
|
-
totalAmount: typeof p.totalAmount === "number" ? p.totalAmount : void 0,
|
|
1539
|
-
overrideAmount: typeof p.overrideAmount === "number" ? p.overrideAmount : null,
|
|
1540
|
-
currency: typeof p.currency === "string" ? p.currency : void 0,
|
|
1541
|
-
metadata: p.metadata ?? null
|
|
1542
|
-
})),
|
|
1543
|
-
successUrl: raw.successUrl,
|
|
1544
|
-
cancelUrl: raw.cancelUrl,
|
|
1545
|
-
coupons: raw.coupons,
|
|
1546
|
-
subtotalAmount: raw.subtotalAmount,
|
|
1547
|
-
discountAmount: raw.discountAmount,
|
|
1548
|
-
totalAmount: raw.totalAmount,
|
|
1549
|
-
createdAt: raw.createdAt,
|
|
1550
|
-
gateways: raw.gateways,
|
|
1551
|
-
accountData: raw.accountData,
|
|
1552
|
-
tagsData: raw.tagsData
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
/**
|
|
1556
|
-
* Coerce a raw vault block into a typed {@link VaultCaptureBlock}. The
|
|
1557
|
-
* server-only PCIVault submit `secret` is deliberately dropped so it never
|
|
1558
|
-
* lands on the public session surface (logs / telemetry / client inspection).
|
|
1559
|
-
*/
|
|
1560
|
-
toVaultBlock(raw) {
|
|
1561
|
-
return {
|
|
1562
|
-
html: typeof raw.html === "string" ? raw.html : void 0,
|
|
1563
|
-
url: typeof raw.url === "string" ? raw.url : void 0,
|
|
1564
|
-
messageToken: typeof raw.messageToken === "string" ? raw.messageToken : void 0,
|
|
1565
|
-
expectedOrigin: typeof raw.expectedOrigin === "string" ? raw.expectedOrigin : void 0
|
|
1566
|
-
};
|
|
1567
|
-
}
|
|
1568
|
-
toCheckoutSessionStatus(status) {
|
|
1569
|
-
if (status === "completed") {
|
|
1570
|
-
return "complete";
|
|
1571
|
-
}
|
|
1572
|
-
if (status === "expired") {
|
|
1573
|
-
return "expired";
|
|
1574
|
-
}
|
|
1575
|
-
return "open";
|
|
1576
|
-
}
|
|
1577
|
-
async resolveProcessResponse(response, checkoutSessionId, options) {
|
|
1578
|
-
if (response.status !== 202) {
|
|
1579
|
-
return response;
|
|
1580
|
-
}
|
|
1581
|
-
const payload = await response.json().catch(() => null);
|
|
1582
|
-
const pending = this.toCheckoutProcessingPending(payload, response, checkoutSessionId);
|
|
1583
|
-
const session = await this.waitForCheckoutSessionCompletion(pending.sessionId, {
|
|
1584
|
-
initialDelayMs: pending.retryAfterMs,
|
|
1585
|
-
timeoutMs: options?.pollTimeoutMs,
|
|
1586
|
-
nonce: options?.nonce
|
|
1587
|
-
});
|
|
1588
|
-
if (session.data.session?.status === "complete") {
|
|
1589
|
-
return new Response(null, { status: 204, statusText: "No Content" });
|
|
1590
|
-
}
|
|
1591
|
-
if (session.data.session?.status === "expired") {
|
|
1592
|
-
throw new import_shared2.FloPayError(
|
|
1593
|
-
"Checkout session has expired.",
|
|
1594
|
-
"api_error",
|
|
1595
|
-
{ code: "checkout_session_expired" }
|
|
1596
|
-
);
|
|
1597
|
-
}
|
|
1598
|
-
throw createCheckoutProcessingTimeoutError();
|
|
1599
|
-
}
|
|
1600
|
-
toCheckoutProcessingPending(payload, response, checkoutSessionId) {
|
|
1601
|
-
const retryAfterHeader = response.headers.get("Retry-After");
|
|
1602
|
-
const headerRetryAfterSeconds = retryAfterHeader === null || retryAfterHeader.trim() === "" ? void 0 : Number(retryAfterHeader);
|
|
1603
|
-
const headerRetryAfterMs = headerRetryAfterSeconds !== void 0 && Number.isFinite(headerRetryAfterSeconds) ? headerRetryAfterSeconds * 1e3 : void 0;
|
|
1604
|
-
return {
|
|
1605
|
-
type: "checkout_processing",
|
|
1606
|
-
sessionId: readString(payload, "sessionId") ?? checkoutSessionId,
|
|
1607
|
-
retryAfterMs: this.clampRetryAfterMs(
|
|
1608
|
-
readNumber(payload, "retryAfterMs") ?? headerRetryAfterMs ?? DEFAULT_PROCESSING_RETRY_AFTER_MS
|
|
1609
|
-
),
|
|
1610
|
-
statusUrl: readString(payload, "statusUrl"),
|
|
1611
|
-
sessionUrl: readString(payload, "sessionUrl")
|
|
1612
|
-
};
|
|
1613
|
-
}
|
|
1614
|
-
clampRetryAfterMs(retryAfterMs) {
|
|
1615
|
-
return Math.max(0, Math.min(retryAfterMs, MAX_PROCESSING_RETRY_AFTER_MS));
|
|
1616
|
-
}
|
|
1617
|
-
/**
|
|
1618
|
-
* Stash the display-only fields the consumer passed into a create-session
|
|
1619
|
-
* call. Runs after the backend assigns a UUID so a later GET on the same
|
|
1620
|
-
* session (typically after a redirect) can fill in fields the backend no
|
|
1621
|
-
* longer persists — `overrideAmount`, `totalAmount`, `name`, etc.
|
|
1622
|
-
*
|
|
1623
|
-
* No-op when no UUID is available.
|
|
1624
|
-
*/
|
|
1625
|
-
autoCacheDisplayData(sessionId, params) {
|
|
1626
|
-
if (!sessionId) return;
|
|
1627
|
-
const products = params.products ?? (0, import_shared2.foldIntoProducts)(params.items, params.subscriptions);
|
|
1628
|
-
if (products.length === 0 && !params.currency) {
|
|
1629
|
-
return;
|
|
1630
|
-
}
|
|
1631
|
-
const usingUnifiedProducts = params.products !== void 0;
|
|
1632
|
-
const sessionCurrency = (0, import_shared2.resolveSessionCurrency)(
|
|
1633
|
-
params.currency,
|
|
1634
|
-
usingUnifiedProducts ? void 0 : params.items,
|
|
1635
|
-
usingUnifiedProducts ? void 0 : params.subscriptions,
|
|
1636
|
-
products
|
|
1637
|
-
);
|
|
1638
|
-
cacheSessionDisplayData(sessionId, {
|
|
1639
|
-
currency: sessionCurrency ?? void 0,
|
|
1640
|
-
products: products.map((p) => ({
|
|
1641
|
-
code: p.code ?? p.providerItemId ?? p.providerPlanId,
|
|
1642
|
-
type: p.type,
|
|
1643
|
-
name: p.name ?? p.itemName ?? p.providerItemName ?? p.subscriptionName ?? p.providerPlanName ?? null,
|
|
1644
|
-
totalAmount: p.totalAmount,
|
|
1645
|
-
overrideAmount: p.overrideAmount,
|
|
1646
|
-
currency: p.currency ?? sessionCurrency ?? void 0
|
|
1647
|
-
}))
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Merge cached display-only fields (set by {@link cacheSessionDisplayData})
|
|
1652
|
-
* into a raw session response. Server values always win — cache fills in
|
|
1653
|
-
* only where the server returned `null` / `undefined`.
|
|
1654
|
-
*/
|
|
1655
|
-
mergeCachedDisplayData(raw) {
|
|
1656
|
-
const cached = getSessionDisplayData(raw.uuid);
|
|
1657
|
-
const cachedProducts = /* @__PURE__ */ new Map();
|
|
1658
|
-
const productKey = (code) => code ? `code:${code}` : void 0;
|
|
1659
|
-
for (const p of cached?.products ?? []) {
|
|
1660
|
-
const key = productKey(p.code);
|
|
1661
|
-
if (key) cachedProducts.set(key, p);
|
|
1662
|
-
}
|
|
1663
|
-
const mergedProducts = (raw.products ?? []).map((p) => {
|
|
1664
|
-
const key = productKey(p.code);
|
|
1665
|
-
const fallback = key ? cachedProducts.get(key) : void 0;
|
|
1666
|
-
return {
|
|
1667
|
-
...p,
|
|
1668
|
-
name: p.name ?? fallback?.name ?? null,
|
|
1669
|
-
totalAmount: p.totalAmount ?? fallback?.totalAmount,
|
|
1670
|
-
overrideAmount: p.overrideAmount ?? fallback?.overrideAmount,
|
|
1671
|
-
currency: p.currency ?? fallback?.currency
|
|
1672
|
-
};
|
|
1673
|
-
});
|
|
1674
|
-
return {
|
|
1675
|
-
...raw,
|
|
1676
|
-
currency: raw.currency ?? cached?.currency,
|
|
1677
|
-
products: mergedProducts
|
|
1678
|
-
};
|
|
1679
|
-
}
|
|
1680
|
-
};
|
|
1681
|
-
function createInstrumentedPaymentAPI(billingApiUrl, hooks) {
|
|
1682
|
-
const InstrumentedPaymentAPI = PaymentAPI;
|
|
1683
|
-
return new InstrumentedPaymentAPI(billingApiUrl, hooks);
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
// src/stripe-adapter.ts
|
|
1687
|
-
function toStripeElementType(type) {
|
|
1688
|
-
const map = {
|
|
1689
|
-
payment: "payment",
|
|
1690
|
-
address: "address"
|
|
1691
|
-
};
|
|
1692
|
-
return map[type];
|
|
1693
|
-
}
|
|
1694
|
-
function toStripeAppearanceTheme(theme) {
|
|
1695
|
-
switch (theme) {
|
|
1696
|
-
case "night":
|
|
1697
|
-
return "night";
|
|
1698
|
-
case "flat":
|
|
1699
|
-
return "flat";
|
|
1700
|
-
// 'default', 'none', undefined, or any unexpected value → Stripe's baseline.
|
|
1701
|
-
default:
|
|
1702
|
-
return "stripe";
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
function paymentMethodTypesKey(paymentMethodTypes) {
|
|
1706
|
-
return paymentMethodTypes ? JSON.stringify(paymentMethodTypes.map((paymentMethodType) => paymentMethodType.trim().toLowerCase())) : null;
|
|
1707
|
-
}
|
|
1708
|
-
function wrapStripeElement(stripeElement) {
|
|
1709
|
-
const el = stripeElement;
|
|
1710
|
-
return {
|
|
1711
|
-
mount(container) {
|
|
1712
|
-
el.mount(container);
|
|
1713
|
-
},
|
|
1714
|
-
unmount() {
|
|
1715
|
-
el.unmount();
|
|
1716
|
-
},
|
|
1717
|
-
update(options) {
|
|
1718
|
-
el.update(options);
|
|
1719
|
-
},
|
|
1720
|
-
on(event, handler) {
|
|
1721
|
-
el["on"]?.(event, handler);
|
|
1722
|
-
},
|
|
1723
|
-
off(event, handler) {
|
|
1724
|
-
el["off"]?.(event, handler);
|
|
1725
|
-
},
|
|
1726
|
-
destroy() {
|
|
1727
|
-
el.destroy();
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
function toStripeBillingDetails(billing) {
|
|
1732
|
-
return {
|
|
1733
|
-
billing_details: {
|
|
1734
|
-
...billing.email ? { email: billing.email } : {},
|
|
1735
|
-
...billing.name ? { name: billing.name } : {},
|
|
1736
|
-
...billing.address ? {
|
|
1737
|
-
address: {
|
|
1738
|
-
...billing.address.country ? { country: billing.address.country } : {},
|
|
1739
|
-
...billing.address.postal_code ? { postal_code: billing.address.postal_code } : {},
|
|
1740
|
-
...billing.address.city ? { city: billing.address.city } : {},
|
|
1741
|
-
...billing.address.line1 ? { line1: billing.address.line1 } : {},
|
|
1742
|
-
...billing.address.line2 ? { line2: billing.address.line2 } : {},
|
|
1743
|
-
...billing.address.state ? { state: billing.address.state } : {}
|
|
1744
|
-
}
|
|
1745
|
-
} : {}
|
|
1746
|
-
}
|
|
1747
|
-
};
|
|
1748
|
-
}
|
|
1749
|
-
var StripeAdapter = class {
|
|
1750
|
-
constructor() {
|
|
1751
|
-
this.name = "stripe";
|
|
1752
|
-
this.stripe = null;
|
|
1753
|
-
this.elements = null;
|
|
1754
|
-
// Serialized appearance currently applied to `this.elements`. Used to detect
|
|
1755
|
-
// when consumers swap themes mid-session so we can live-update the Stripe
|
|
1756
|
-
// Elements group instead of returning a stale-styled cache. `null` while no
|
|
1757
|
-
// elements group exists.
|
|
1758
|
-
this.appliedAppearanceKey = null;
|
|
1759
|
-
this.appliedPaymentMethodTypesKey = null;
|
|
1760
|
-
this.appliedClientSecret = null;
|
|
1761
|
-
this.verifiedClientSecret = null;
|
|
1762
|
-
this.verifiedPaymentMethodTypesKey = null;
|
|
1763
|
-
}
|
|
1764
|
-
async initialize(config) {
|
|
1765
|
-
if (typeof window === "undefined") {
|
|
1766
|
-
return;
|
|
1767
|
-
}
|
|
1768
|
-
const stripe = await (0, import_stripe_js.loadStripe)(config.publishableKey, {
|
|
1769
|
-
locale: config.locale ?? "auto"
|
|
1770
|
-
});
|
|
1771
|
-
if (!stripe) {
|
|
1772
|
-
throw new import_shared3.FloPayError(
|
|
1773
|
-
"Failed to initialize Stripe. Check your publishable key.",
|
|
1774
|
-
"authentication_error"
|
|
1775
|
-
);
|
|
1776
|
-
}
|
|
1777
|
-
this.stripe = stripe;
|
|
1778
|
-
}
|
|
1779
|
-
/** Lazily creates the Stripe Elements group for the given options. */
|
|
1780
|
-
getElements(options) {
|
|
1781
|
-
if (!this.stripe) {
|
|
1782
|
-
throw new import_shared3.FloPayError(
|
|
1783
|
-
"StripeAdapter not initialized. Call initialize() first.",
|
|
1784
|
-
"api_error"
|
|
1785
|
-
);
|
|
1786
|
-
}
|
|
1787
|
-
const stripeAppearance = options?.appearance ? {
|
|
1788
|
-
theme: toStripeAppearanceTheme(options.appearance.theme),
|
|
1789
|
-
variables: options.appearance.variables,
|
|
1790
|
-
rules: options.appearance.rules
|
|
1791
|
-
} : void 0;
|
|
1792
|
-
const nextAppearanceKey = stripeAppearance ? JSON.stringify(stripeAppearance) : null;
|
|
1793
|
-
const nextPaymentMethodTypesKey = paymentMethodTypesKey(options?.paymentMethodTypes);
|
|
1794
|
-
const nextClientSecret = options?.clientSecret ?? null;
|
|
1795
|
-
if (this.elements && nextPaymentMethodTypesKey && (nextPaymentMethodTypesKey !== this.appliedPaymentMethodTypesKey || nextClientSecret !== this.appliedClientSecret)) {
|
|
1796
|
-
this.elements = null;
|
|
1797
|
-
this.appliedAppearanceKey = null;
|
|
1798
|
-
this.appliedPaymentMethodTypesKey = null;
|
|
1799
|
-
this.appliedClientSecret = null;
|
|
1800
|
-
this.verifiedClientSecret = null;
|
|
1801
|
-
this.verifiedPaymentMethodTypesKey = null;
|
|
1802
|
-
}
|
|
1803
|
-
if (!this.elements) {
|
|
1804
|
-
let elementsOptions;
|
|
1805
|
-
const deferredAmount = options?.amount ?? 0;
|
|
1806
|
-
const deferredCurrency = (options?.currency ?? "usd").toLowerCase();
|
|
1807
|
-
const paymentMethodCreation = options?.paymentMethodCreation ?? "manual";
|
|
1808
|
-
if (options?.clientSecret) {
|
|
1809
|
-
elementsOptions = { clientSecret: options.clientSecret };
|
|
1810
|
-
} else if (deferredAmount > 0) {
|
|
1811
|
-
elementsOptions = {
|
|
1812
|
-
mode: "payment",
|
|
1813
|
-
amount: deferredAmount,
|
|
1814
|
-
currency: deferredCurrency,
|
|
1815
|
-
paymentMethodCreation
|
|
1816
|
-
};
|
|
1817
|
-
if (options?.setupFutureUsage) {
|
|
1818
|
-
elementsOptions["setupFutureUsage"] = options.setupFutureUsage;
|
|
1819
|
-
}
|
|
1820
|
-
} else {
|
|
1821
|
-
elementsOptions = {
|
|
1822
|
-
mode: "setup",
|
|
1823
|
-
currency: deferredCurrency,
|
|
1824
|
-
paymentMethodCreation
|
|
1825
|
-
};
|
|
1826
|
-
}
|
|
1827
|
-
if (!options?.clientSecret && options?.paymentMethodTypes) {
|
|
1828
|
-
elementsOptions["paymentMethodTypes"] = options.paymentMethodTypes;
|
|
1829
|
-
}
|
|
1830
|
-
if (stripeAppearance) {
|
|
1831
|
-
elementsOptions["appearance"] = stripeAppearance;
|
|
1832
|
-
}
|
|
1833
|
-
this.elements = this.stripe.elements(elementsOptions);
|
|
1834
|
-
this.appliedAppearanceKey = nextAppearanceKey;
|
|
1835
|
-
this.appliedPaymentMethodTypesKey = nextPaymentMethodTypesKey;
|
|
1836
|
-
this.appliedClientSecret = nextClientSecret;
|
|
1837
|
-
this.verifiedClientSecret = null;
|
|
1838
|
-
this.verifiedPaymentMethodTypesKey = null;
|
|
1839
|
-
} else if (nextAppearanceKey !== this.appliedAppearanceKey) {
|
|
1840
|
-
this.elements.update({
|
|
1841
|
-
appearance: stripeAppearance ?? {}
|
|
1842
|
-
});
|
|
1843
|
-
this.appliedAppearanceKey = nextAppearanceKey;
|
|
1844
|
-
}
|
|
1845
|
-
return this.elements;
|
|
1846
|
-
}
|
|
1847
|
-
async assertClientSecretPaymentMethods(clientSecret, allowedPaymentMethodTypes) {
|
|
1848
|
-
if (!this.stripe) {
|
|
1849
|
-
throw new import_shared3.FloPayError(
|
|
1850
|
-
"StripeAdapter not initialized. Call initialize() first.",
|
|
1851
|
-
"api_error"
|
|
1852
|
-
);
|
|
1853
|
-
}
|
|
1854
|
-
let intent;
|
|
1855
|
-
let retrievalFailed = false;
|
|
1856
|
-
if ((0, import_shared3.isSetupIntentClientSecret)(clientSecret)) {
|
|
1857
|
-
const { setupIntent, error } = await this.stripe.retrieveSetupIntent(clientSecret);
|
|
1858
|
-
intent = setupIntent;
|
|
1859
|
-
retrievalFailed = Boolean(error);
|
|
1860
|
-
} else {
|
|
1861
|
-
const { paymentIntent, error } = await this.stripe.retrievePaymentIntent(clientSecret);
|
|
1862
|
-
intent = paymentIntent;
|
|
1863
|
-
retrievalFailed = Boolean(error);
|
|
1864
|
-
}
|
|
1865
|
-
const providerPaymentMethodTypes = intent?.payment_method_types;
|
|
1866
|
-
if (retrievalFailed || !Array.isArray(providerPaymentMethodTypes)) {
|
|
1867
|
-
throw new import_shared3.FloPayError(
|
|
1868
|
-
"Unable to verify the payment methods configured for this client secret.",
|
|
1869
|
-
"api_error",
|
|
1870
|
-
{ param: "clientSecret" }
|
|
1871
|
-
);
|
|
1872
|
-
}
|
|
1873
|
-
const allowlist = new Set(
|
|
1874
|
-
allowedPaymentMethodTypes.map((paymentMethodType) => paymentMethodType.toLowerCase())
|
|
1875
|
-
);
|
|
1876
|
-
const hasDisallowedProviderMethod = providerPaymentMethodTypes.some(
|
|
1877
|
-
(paymentMethodType) => typeof paymentMethodType !== "string" || !allowlist.has(paymentMethodType.trim().toLowerCase())
|
|
1878
|
-
);
|
|
1879
|
-
if (hasDisallowedProviderMethod || providerPaymentMethodTypes.length === 0) {
|
|
1880
|
-
throw new import_shared3.FloPayError(
|
|
1881
|
-
"The client-secret intent must enable only declared non-card payment methods.",
|
|
1882
|
-
"validation_error",
|
|
1883
|
-
{ param: "clientSecret" }
|
|
1884
|
-
);
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
async createElement(type, options) {
|
|
1888
|
-
let resolvedOptions = options;
|
|
1889
|
-
if (type === "payment") {
|
|
1890
|
-
const paymentMethodTypes = options.paymentMethodTypes?.map((paymentMethodType) => paymentMethodType.trim()).filter((paymentMethodType) => paymentMethodType && paymentMethodType.toLowerCase() !== "card");
|
|
1891
|
-
if (!paymentMethodTypes?.length) {
|
|
1892
|
-
throw new import_shared3.FloPayError(
|
|
1893
|
-
"At least one supported non-card payment method is required.",
|
|
1894
|
-
"validation_error",
|
|
1895
|
-
{ param: "paymentMethodTypes" }
|
|
1896
|
-
);
|
|
1897
|
-
}
|
|
1898
|
-
resolvedOptions = { ...options, paymentMethodTypes };
|
|
1899
|
-
if (resolvedOptions.clientSecret) {
|
|
1900
|
-
const resolvedPaymentMethodTypesKey = paymentMethodTypesKey(paymentMethodTypes);
|
|
1901
|
-
const verificationIsCached = Boolean(
|
|
1902
|
-
this.elements && this.appliedClientSecret === resolvedOptions.clientSecret && this.appliedPaymentMethodTypesKey === resolvedPaymentMethodTypesKey && this.verifiedClientSecret === resolvedOptions.clientSecret && this.verifiedPaymentMethodTypesKey === resolvedPaymentMethodTypesKey
|
|
1903
|
-
);
|
|
1904
|
-
if (!verificationIsCached) {
|
|
1905
|
-
await this.assertClientSecretPaymentMethods(
|
|
1906
|
-
resolvedOptions.clientSecret,
|
|
1907
|
-
paymentMethodTypes
|
|
1908
|
-
);
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
const elements = this.getElements(resolvedOptions);
|
|
1913
|
-
if (type === "payment" && resolvedOptions.clientSecret) {
|
|
1914
|
-
this.verifiedClientSecret = resolvedOptions.clientSecret;
|
|
1915
|
-
this.verifiedPaymentMethodTypesKey = paymentMethodTypesKey(
|
|
1916
|
-
resolvedOptions.paymentMethodTypes
|
|
1917
|
-
);
|
|
1918
|
-
}
|
|
1919
|
-
const stripeType = toStripeElementType(type);
|
|
1920
|
-
const elementOptions = {};
|
|
1921
|
-
if (resolvedOptions.layout) {
|
|
1922
|
-
elementOptions["layout"] = resolvedOptions.layout;
|
|
1923
|
-
}
|
|
1924
|
-
if (resolvedOptions.defaultValues) {
|
|
1925
|
-
elementOptions["defaultValues"] = resolvedOptions.defaultValues;
|
|
1926
|
-
}
|
|
1927
|
-
if (resolvedOptions.readOnly) {
|
|
1928
|
-
elementOptions["readOnly"] = resolvedOptions.readOnly;
|
|
1929
|
-
}
|
|
1930
|
-
if (resolvedOptions.mode) {
|
|
1931
|
-
elementOptions["mode"] = resolvedOptions.mode;
|
|
1932
|
-
}
|
|
1933
|
-
const stripeElement = elements.create(stripeType, elementOptions);
|
|
1934
|
-
return wrapStripeElement(stripeElement);
|
|
1935
|
-
}
|
|
1936
|
-
getElement(type) {
|
|
1937
|
-
if (!this.elements) return null;
|
|
1938
|
-
const stripeType = toStripeElementType(type);
|
|
1939
|
-
const existing = this.elements.getElement(stripeType);
|
|
1940
|
-
if (!existing) return null;
|
|
1941
|
-
return wrapStripeElement(existing);
|
|
1942
|
-
}
|
|
1943
|
-
async submitElements() {
|
|
1944
|
-
if (!this.stripe || !this.elements) {
|
|
1945
|
-
return { error: new import_shared3.FloPayError("Stripe not initialized", "api_error") };
|
|
1946
|
-
}
|
|
1947
|
-
const { error } = await this.elements.submit();
|
|
1948
|
-
if (error) {
|
|
1949
|
-
return {
|
|
1950
|
-
error: new import_shared3.FloPayError(error.message ?? "Validation failed", "validation_error")
|
|
1951
|
-
};
|
|
1952
|
-
}
|
|
1953
|
-
return {};
|
|
1954
|
-
}
|
|
1955
|
-
async confirmPayment(params) {
|
|
1956
|
-
if (!this.stripe || !this.elements) {
|
|
1957
|
-
throw new import_shared3.FloPayError(
|
|
1958
|
-
"StripeAdapter not initialized or no elements created.",
|
|
1959
|
-
"api_error"
|
|
1960
|
-
);
|
|
1961
|
-
}
|
|
1962
|
-
const billing = params.billingDetails;
|
|
1963
|
-
const paymentMethodData = billing ? toStripeBillingDetails(billing) : void 0;
|
|
1964
|
-
const { error, paymentIntent } = await this.stripe.confirmPayment({
|
|
1965
|
-
elements: this.elements,
|
|
1966
|
-
clientSecret: params.clientSecret,
|
|
1967
|
-
confirmParams: {
|
|
1968
|
-
return_url: params.returnUrl ?? window.location.href,
|
|
1969
|
-
...paymentMethodData ? { payment_method_data: paymentMethodData } : {}
|
|
1970
|
-
},
|
|
1971
|
-
redirect: "if_required"
|
|
1972
|
-
});
|
|
1973
|
-
if (error) {
|
|
1974
|
-
return {
|
|
1975
|
-
status: "failed",
|
|
1976
|
-
error: new import_shared3.FloPayError(
|
|
1977
|
-
error.message ?? "Payment failed",
|
|
1978
|
-
"api_error",
|
|
1979
|
-
{
|
|
1980
|
-
code: error.code,
|
|
1981
|
-
declineCode: error.decline_code
|
|
1982
|
-
}
|
|
1983
|
-
)
|
|
1984
|
-
};
|
|
1985
|
-
}
|
|
1986
|
-
if (!paymentIntent) {
|
|
1987
|
-
return { status: "failed", error: new import_shared3.FloPayError("No payment intent returned", "api_error") };
|
|
1988
|
-
}
|
|
1989
|
-
const statusMap = {
|
|
1990
|
-
succeeded: "succeeded",
|
|
1991
|
-
processing: "processing",
|
|
1992
|
-
requires_action: "requires_action",
|
|
1993
|
-
requires_payment_method: "failed",
|
|
1994
|
-
canceled: "failed"
|
|
1995
|
-
};
|
|
1996
|
-
return {
|
|
1997
|
-
status: statusMap[paymentIntent.status] ?? "failed",
|
|
1998
|
-
paymentIntentId: paymentIntent.id,
|
|
1999
|
-
paymentMethodId: this.extractPaymentMethodId(paymentIntent.payment_method)
|
|
2000
|
-
};
|
|
2001
|
-
}
|
|
2002
|
-
extractPaymentMethodId(paymentMethod) {
|
|
2003
|
-
if (typeof paymentMethod === "string" && paymentMethod.startsWith("pm_")) {
|
|
2004
|
-
return paymentMethod;
|
|
2005
|
-
}
|
|
2006
|
-
if (paymentMethod && typeof paymentMethod === "object" && typeof paymentMethod.id === "string") {
|
|
2007
|
-
return paymentMethod.id;
|
|
2008
|
-
}
|
|
2009
|
-
return void 0;
|
|
2010
|
-
}
|
|
2011
|
-
async confirmPayPalPayment(params) {
|
|
2012
|
-
if (!this.stripe) {
|
|
2013
|
-
return { status: "failed", error: new import_shared3.FloPayError("Stripe not initialized", "api_error") };
|
|
2014
|
-
}
|
|
2015
|
-
const baseUrl = params.billingApiUrl.replace(/\/+$/, "");
|
|
2016
|
-
if (this.elements) {
|
|
2017
|
-
const { error: submitError } = await this.elements.submit();
|
|
2018
|
-
if (submitError) {
|
|
2019
|
-
return {
|
|
2020
|
-
status: "failed",
|
|
2021
|
-
error: new import_shared3.FloPayError(
|
|
2022
|
-
submitError.message ?? "PayPal payment failed",
|
|
2023
|
-
"validation_error",
|
|
2024
|
-
{ code: submitError.code }
|
|
2025
|
-
)
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
let intentClientSecret;
|
|
2030
|
-
try {
|
|
2031
|
-
const intent = await new PaymentAPI(baseUrl).createSessionIntent(
|
|
2032
|
-
params.sessionId,
|
|
2033
|
-
params.nonce ?? "",
|
|
2034
|
-
{
|
|
2035
|
-
provider: "stripe",
|
|
2036
|
-
paymentMethodCategory: "wallet",
|
|
2037
|
-
paymentMethodType: "paypal",
|
|
2038
|
-
paymentMethodId: null,
|
|
2039
|
-
intentKind: "payment"
|
|
2040
|
-
}
|
|
2041
|
-
);
|
|
2042
|
-
if (intent.provider !== "stripe") {
|
|
2043
|
-
throw new import_shared3.FloPayError("Invalid provider returned for PayPal intent", "api_error");
|
|
2044
|
-
}
|
|
2045
|
-
intentClientSecret = intent.clientSecret;
|
|
2046
|
-
} catch (error2) {
|
|
2047
|
-
return {
|
|
2048
|
-
status: "failed",
|
|
2049
|
-
error: error2 instanceof import_shared3.FloPayError ? error2 : new import_shared3.FloPayError("Failed to create PayPal payment intent", "api_error")
|
|
2050
|
-
};
|
|
2051
|
-
}
|
|
2052
|
-
const { error } = await this.stripe.confirmPayment({
|
|
2053
|
-
clientSecret: intentClientSecret,
|
|
2054
|
-
elements: this.elements ?? void 0,
|
|
2055
|
-
confirmParams: { return_url: params.returnUrl }
|
|
2056
|
-
});
|
|
2057
|
-
if (error) {
|
|
2058
|
-
if (params.nonce) {
|
|
2059
|
-
try {
|
|
2060
|
-
await new PaymentAPI(baseUrl).reportSessionIntentDecline(
|
|
2061
|
-
params.sessionId,
|
|
2062
|
-
params.nonce,
|
|
2063
|
-
{
|
|
2064
|
-
provider: "stripe",
|
|
2065
|
-
paymentMethodCategory: "wallet",
|
|
2066
|
-
paymentMethodType: "paypal",
|
|
2067
|
-
providerDeclineReason: error.code ?? "provider_declined"
|
|
2068
|
-
}
|
|
2069
|
-
);
|
|
2070
|
-
} catch {
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
return {
|
|
2074
|
-
status: "failed",
|
|
2075
|
-
error: new import_shared3.FloPayError(error.message ?? "PayPal payment failed", "api_error", { code: error.code })
|
|
2076
|
-
};
|
|
2077
|
-
}
|
|
2078
|
-
return {
|
|
2079
|
-
status: "processing"
|
|
2080
|
-
};
|
|
2081
|
-
}
|
|
2082
|
-
async resumePayPalPayment() {
|
|
2083
|
-
if (!this.stripe || typeof window === "undefined") return null;
|
|
2084
|
-
const params = new URLSearchParams(window.location.search);
|
|
2085
|
-
const paymentIntentId = params.get("payment_intent");
|
|
2086
|
-
const clientSecret = params.get("payment_intent_client_secret");
|
|
2087
|
-
const redirectStatus = params.get("redirect_status");
|
|
2088
|
-
if (!paymentIntentId || !clientSecret) return null;
|
|
2089
|
-
if (redirectStatus === "failed") {
|
|
2090
|
-
return {
|
|
2091
|
-
status: "failed",
|
|
2092
|
-
error: new import_shared3.FloPayError("PayPal payment was declined. Please try again.", "api_error")
|
|
2093
|
-
};
|
|
2094
|
-
}
|
|
2095
|
-
const { paymentIntent, error } = await this.stripe.retrievePaymentIntent(clientSecret);
|
|
2096
|
-
if (error) {
|
|
2097
|
-
return {
|
|
2098
|
-
status: "failed",
|
|
2099
|
-
error: new import_shared3.FloPayError(error.message ?? "Failed to retrieve PayPal payment", "api_error")
|
|
2100
|
-
};
|
|
2101
|
-
}
|
|
2102
|
-
if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded")) {
|
|
2103
|
-
const pmId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
|
|
2104
|
-
const url = new URL(window.location.href);
|
|
2105
|
-
url.searchParams.delete("payment_intent");
|
|
2106
|
-
url.searchParams.delete("payment_intent_client_secret");
|
|
2107
|
-
url.searchParams.delete("redirect_status");
|
|
2108
|
-
window.history.replaceState({}, "", url.toString());
|
|
2109
|
-
return {
|
|
2110
|
-
status: paymentIntent.status,
|
|
2111
|
-
paymentIntentId: paymentIntent.id,
|
|
2112
|
-
paymentMethodId: pmId
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
return {
|
|
2116
|
-
status: "failed",
|
|
2117
|
-
error: new import_shared3.FloPayError("PayPal payment was not completed. Please try again.", "api_error")
|
|
2118
|
-
};
|
|
2119
|
-
}
|
|
2120
|
-
getRawProvider() {
|
|
2121
|
-
return this.stripe;
|
|
2122
|
-
}
|
|
2123
|
-
createPayPalElements(options) {
|
|
2124
|
-
if (!this.stripe) return null;
|
|
2125
|
-
const elementsOptions = {
|
|
2126
|
-
mode: "payment",
|
|
2127
|
-
amount: options.amount ?? 0,
|
|
2128
|
-
currency: (options.currency ?? "usd").toLowerCase(),
|
|
2129
|
-
captureMethod: "manual"
|
|
2130
|
-
};
|
|
2131
|
-
if (options.setupFutureUsage) {
|
|
2132
|
-
elementsOptions["setupFutureUsage"] = options.setupFutureUsage;
|
|
2133
|
-
}
|
|
2134
|
-
if (options.appearance) {
|
|
2135
|
-
elementsOptions["appearance"] = {
|
|
2136
|
-
theme: toStripeAppearanceTheme(options.appearance.theme),
|
|
2137
|
-
variables: options.appearance.variables,
|
|
2138
|
-
rules: options.appearance.rules
|
|
2139
|
-
};
|
|
2140
|
-
}
|
|
2141
|
-
return this.stripe.elements(elementsOptions);
|
|
2142
|
-
}
|
|
2143
|
-
destroy() {
|
|
2144
|
-
this.elements = null;
|
|
2145
|
-
this.appliedAppearanceKey = null;
|
|
2146
|
-
this.appliedPaymentMethodTypesKey = null;
|
|
2147
|
-
this.appliedClientSecret = null;
|
|
2148
|
-
this.verifiedClientSecret = null;
|
|
2149
|
-
this.verifiedPaymentMethodTypesKey = null;
|
|
2150
|
-
this.stripe = null;
|
|
2151
|
-
}
|
|
2152
|
-
};
|
|
2153
|
-
|
|
2154
|
-
// src/flopay.ts
|
|
2155
|
-
var import_shared6 = require("@flopay/shared");
|
|
2156
|
-
|
|
2157
|
-
// src/elements.ts
|
|
2158
|
-
var import_shared4 = require("@flopay/shared");
|
|
2159
|
-
var FloPayElements = class {
|
|
2160
|
-
constructor(provider, options) {
|
|
2161
|
-
this.elementMap = /* @__PURE__ */ new Map();
|
|
2162
|
-
this.provider = provider;
|
|
2163
|
-
this.baseOptions = options ?? {};
|
|
2164
|
-
}
|
|
2165
|
-
/**
|
|
2166
|
-
* Creates a new element of the given type.
|
|
2167
|
-
* If an element of that type already exists, it is destroyed first.
|
|
2168
|
-
*/
|
|
2169
|
-
async create(type, options) {
|
|
2170
|
-
const merged = { ...this.baseOptions, ...options };
|
|
2171
|
-
if (type === "payment") {
|
|
2172
|
-
const paymentMethodTypes = merged.paymentMethodTypes?.map((paymentMethodType) => paymentMethodType.trim()).filter((paymentMethodType) => paymentMethodType && paymentMethodType.toLowerCase() !== "card");
|
|
2173
|
-
if (!paymentMethodTypes?.length) {
|
|
2174
|
-
throw new import_shared4.FloPayError(
|
|
2175
|
-
"At least one supported non-card payment method is required.",
|
|
2176
|
-
"validation_error",
|
|
2177
|
-
{ param: "paymentMethodTypes" }
|
|
2178
|
-
);
|
|
2179
|
-
}
|
|
2180
|
-
merged.paymentMethodTypes = paymentMethodTypes;
|
|
2181
|
-
}
|
|
2182
|
-
const providerExisting = this.provider.getElement(type);
|
|
2183
|
-
if (providerExisting) {
|
|
2184
|
-
this.elementMap.set(type, providerExisting);
|
|
2185
|
-
return providerExisting;
|
|
2186
|
-
}
|
|
2187
|
-
const existing = this.elementMap.get(type);
|
|
2188
|
-
if (existing) {
|
|
2189
|
-
existing.destroy();
|
|
2190
|
-
}
|
|
2191
|
-
const element = await this.provider.createElement(type, merged);
|
|
2192
|
-
this.elementMap.set(type, element);
|
|
2193
|
-
return element;
|
|
2194
|
-
}
|
|
2195
|
-
/** Returns a previously created element, or `null`. */
|
|
2196
|
-
getElement(type) {
|
|
2197
|
-
return this.elementMap.get(type) ?? null;
|
|
2198
|
-
}
|
|
2199
|
-
/**
|
|
2200
|
-
* Submits all mounted elements for validation.
|
|
2201
|
-
*
|
|
2202
|
-
* Returns an object with an optional error if validation fails.
|
|
2203
|
-
* This does NOT confirm the payment — call `floPay.confirmPayment()` for that.
|
|
2204
|
-
*/
|
|
2205
|
-
async submit() {
|
|
2206
|
-
return {};
|
|
2207
|
-
}
|
|
2208
|
-
/** Destroys all created elements and clears the internal map. */
|
|
2209
|
-
destroy() {
|
|
2210
|
-
for (const element of this.elementMap.values()) {
|
|
2211
|
-
element.destroy();
|
|
2212
|
-
}
|
|
2213
|
-
this.elementMap.clear();
|
|
2214
|
-
}
|
|
2215
|
-
};
|
|
2216
|
-
|
|
2217
|
-
// src/pci-vault-card-capture.ts
|
|
2218
|
-
var import_shared5 = require("@flopay/shared");
|
|
2219
|
-
var VAULT_MESSAGE_SOURCE = "flopay-vault";
|
|
2220
|
-
var VAULT_NON_TERMINAL_OUTCOME_LOGS = {
|
|
2221
|
-
ready: ["vault.widget.ready", "vault_ready"],
|
|
2222
|
-
submitting: ["vault.submission.started", "vault_submit"],
|
|
2223
|
-
blocked: ["operation.state_transition", "vault_submit"],
|
|
2224
|
-
action_required: ["vault.action.required", "three_ds_handoff"]
|
|
2225
|
-
};
|
|
2226
|
-
function addBreadcrumb(event) {
|
|
2227
|
-
const data = {
|
|
2228
|
-
class: event.class,
|
|
2229
|
-
stage: event.stage
|
|
2230
|
-
};
|
|
2231
|
-
if ("code" in event) data["code"] = event.code;
|
|
2232
|
-
if ("provider" in event && event.provider) data["provider"] = event.provider;
|
|
2233
|
-
if ("paymentMethodCategory" in event && event.paymentMethodCategory) {
|
|
2234
|
-
data["paymentMethodCategory"] = event.paymentMethodCategory;
|
|
2235
|
-
}
|
|
2236
|
-
if ("outcome" in event) data["outcome"] = event.outcome;
|
|
2237
|
-
if ("durationMs" in event && event.durationMs !== void 0) data["durationMs"] = event.durationMs;
|
|
2238
|
-
if ("durationMode" in event && event.durationMode) data["durationMode"] = event.durationMode;
|
|
2239
|
-
try {
|
|
2240
|
-
const sentry = globalThis.Sentry;
|
|
2241
|
-
sentry?.addBreadcrumb?.({
|
|
2242
|
-
category: "flopay.telemetry",
|
|
2243
|
-
level: event.class === "technical_error" ? "error" : "info",
|
|
2244
|
-
message: event.class === "lifecycle" ? event.name : event.class === "technical_error" ? event.code : event.class === "expected_outcome" ? event.outcome : "sdk.performance",
|
|
2245
|
-
data
|
|
2246
|
-
});
|
|
2247
|
-
} catch {
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
function vaultLog(name, stage) {
|
|
2251
|
-
return (0, import_shared5.buildTelemetryLogEvent)({
|
|
2252
|
-
eventId: "11111111-1111-4111-8111-111111111111",
|
|
2253
|
-
name,
|
|
2254
|
-
stage,
|
|
2255
|
-
sequence: 0,
|
|
2256
|
-
provider: "pcivault",
|
|
2257
|
-
paymentMethodCategory: "card"
|
|
2258
|
-
});
|
|
2259
|
-
}
|
|
2260
|
-
function vaultErrorClassification(submissionStarted) {
|
|
2261
|
-
return submissionStarted ? { errorCode: "VAULT_SUBMIT_FAILED", stage: "vault_submit" } : { errorCode: "VAULT_LOAD_FAILED", stage: "vault_mount" };
|
|
2262
|
-
}
|
|
2263
|
-
function vaultOutcomeBreadcrumb(type, submissionStarted) {
|
|
2264
|
-
if (type === "complete" || type === "decline") {
|
|
2265
|
-
return (0, import_shared5.buildTelemetryTerminalEvent)({
|
|
2266
|
-
eventId: "22222222-2222-4222-8222-222222222222",
|
|
2267
|
-
outcome: type === "complete" ? "payment_succeeded" : "payment_declined",
|
|
2268
|
-
sequence: 0,
|
|
2269
|
-
provider: "pcivault",
|
|
2270
|
-
paymentMethodCategory: "card"
|
|
2271
|
-
});
|
|
2272
|
-
}
|
|
2273
|
-
if (type === "error") {
|
|
2274
|
-
const classification = vaultErrorClassification(submissionStarted);
|
|
2275
|
-
return (0, import_shared5.buildTelemetryErrorEvent)({
|
|
2276
|
-
eventId: "33333333-3333-4333-8333-333333333333",
|
|
2277
|
-
...classification,
|
|
2278
|
-
sequence: 0,
|
|
2279
|
-
provider: "pcivault",
|
|
2280
|
-
paymentMethodCategory: "card"
|
|
2281
|
-
});
|
|
2282
|
-
}
|
|
2283
|
-
const [name, stage] = VAULT_NON_TERMINAL_OUTCOME_LOGS[type];
|
|
2284
|
-
return vaultLog(name, stage);
|
|
2285
|
-
}
|
|
2286
|
-
function isVaultResultMessage(value) {
|
|
2287
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2288
|
-
const record = value;
|
|
2289
|
-
return record["source"] === VAULT_MESSAGE_SOURCE && (record["type"] === "ready" || record["type"] === "submitting" || record["type"] === "blocked" || record["type"] === "complete" || record["type"] === "decline" || record["type"] === "error" || record["type"] === "action_required");
|
|
2290
|
-
}
|
|
2291
|
-
function isVaultValidationMessage(value) {
|
|
2292
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2293
|
-
const record = value;
|
|
2294
|
-
return record["source"] === VAULT_MESSAGE_SOURCE && record["type"] === "validation" && Array.isArray(record["messages"]);
|
|
2295
|
-
}
|
|
2296
|
-
function isVaultResizeMessage(value) {
|
|
2297
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2298
|
-
const record = value;
|
|
2299
|
-
return record["source"] === VAULT_MESSAGE_SOURCE && record["type"] === "resize" && typeof record["height"] === "number" && Number.isFinite(record["height"]);
|
|
2300
|
-
}
|
|
2301
|
-
var PciVaultCardCapture = class {
|
|
2302
|
-
constructor(config = {}, internalTelemetry) {
|
|
2303
|
-
this.provider = "pcivault";
|
|
2304
|
-
this.container = null;
|
|
2305
|
-
this.messageHandler = null;
|
|
2306
|
-
/**
|
|
2307
|
-
* Parent-page-level overlay rendering the provider's verification challenge
|
|
2308
|
-
* (3DS-2 iframe) on `action_required`. Owned by the adapter — not the
|
|
2309
|
-
* widget — so it can sit above the host SDK's processing backdrop, which
|
|
2310
|
-
* would otherwise visually cover an in-widget challenge iframe.
|
|
2311
|
-
*/
|
|
2312
|
-
this.actionOverlay = null;
|
|
2313
|
-
/**
|
|
2314
|
-
* Listener that catches the `flopay-vault-3ds-return` postMessage from the
|
|
2315
|
-
* provider's challenge return page. When the SDK owns the challenge iframe
|
|
2316
|
-
* the return page lives inside *that* iframe (not the widget's), so
|
|
2317
|
-
* `window.parent` is the host page — the widget's existing message
|
|
2318
|
-
* listener can't see it. The SDK forwards completion into the widget via
|
|
2319
|
-
* `action_completed` so the widget kicks `/3ds/complete` immediately
|
|
2320
|
-
* instead of waiting on the eventual provider webhook.
|
|
2321
|
-
*/
|
|
2322
|
-
this.threeDsReturnHandler = null;
|
|
2323
|
-
/** Per-session integrity token to require on outcomes (from mount options). */
|
|
2324
|
-
this.messageToken = null;
|
|
2325
|
-
/** Strict origin to require on outcomes, when configured. */
|
|
2326
|
-
this.expectedOrigin = null;
|
|
2327
|
-
/** Latest merchant theme to push into the (cross-origin) widget. */
|
|
2328
|
-
this.theme = null;
|
|
2329
|
-
/** Latest host submit-gate state to push into the widget (block its submit). */
|
|
2330
|
-
this.submitGateBlocked = false;
|
|
2331
|
-
/** Latest card-field order + autofocus directive to push into the widget. */
|
|
2332
|
-
this.cardFieldOrder = null;
|
|
2333
|
-
this.cardAutoFocus = true;
|
|
2334
|
-
this.captureRequestedAt = 0;
|
|
2335
|
-
this.vaultReadyReported = false;
|
|
2336
|
-
this.submissionStarted = false;
|
|
2337
|
-
this.submissionStartedAt = null;
|
|
2338
|
-
this.listeners = /* @__PURE__ */ new Map();
|
|
2339
|
-
this.config = config;
|
|
2340
|
-
this.ownsTelemetryReporter = !internalTelemetry && config.telemetry !== false;
|
|
2341
|
-
this.telemetryReporter = internalTelemetry?.reporter ?? (this.ownsTelemetryReporter ? new TelemetryReporter({
|
|
2342
|
-
billingApiUrl: (0, import_shared5.resolveBillingApiUrl)(),
|
|
2343
|
-
sdkVersion: import_shared5.SDK_VERSION
|
|
2344
|
-
}) : void 0);
|
|
2345
|
-
this.requestedAt = internalTelemetry?.requestedAt;
|
|
2346
|
-
}
|
|
2347
|
-
async mount(container, options) {
|
|
2348
|
-
if (this.ownsTelemetryReporter && !this.telemetryReporter) {
|
|
2349
|
-
this.telemetryReporter = new TelemetryReporter({
|
|
2350
|
-
billingApiUrl: (0, import_shared5.resolveBillingApiUrl)(),
|
|
2351
|
-
sdkVersion: import_shared5.SDK_VERSION
|
|
2352
|
-
});
|
|
2353
|
-
}
|
|
2354
|
-
const mountedAt = this.telemetryReporter?.now?.() ?? telemetryNow();
|
|
2355
|
-
this.captureRequestedAt = this.requestedAt ?? mountedAt;
|
|
2356
|
-
this.vaultReadyReported = false;
|
|
2357
|
-
this.submissionStarted = false;
|
|
2358
|
-
this.submissionStartedAt = null;
|
|
2359
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
2360
|
-
this.reportVaultLoadFailure();
|
|
2361
|
-
throw new import_shared5.FloPayError(
|
|
2362
|
-
"The vault card form is only available in the browser.",
|
|
2363
|
-
"api_error",
|
|
2364
|
-
{ code: "card_capture_no_window" }
|
|
2365
|
-
);
|
|
2366
|
-
}
|
|
2367
|
-
if (!options?.html || !options.html.trim()) {
|
|
2368
|
-
this.reportVaultLoadFailure();
|
|
2369
|
-
throw new import_shared5.FloPayError(
|
|
2370
|
-
"No vault capture widget HTML was provided to mount the secure card form.",
|
|
2371
|
-
"api_error",
|
|
2372
|
-
{ code: "card_capture_no_widget_html" }
|
|
2373
|
-
);
|
|
2374
|
-
}
|
|
2375
|
-
this.container = container;
|
|
2376
|
-
this.messageToken = options.messageToken ?? null;
|
|
2377
|
-
this.expectedOrigin = options.expectedOrigin ?? this.config.expectedOrigin ?? null;
|
|
2378
|
-
this.theme = options.theme ?? null;
|
|
2379
|
-
try {
|
|
2380
|
-
this.attachMessageListener();
|
|
2381
|
-
this.injectWidget(container, options.html);
|
|
2382
|
-
} catch (error) {
|
|
2383
|
-
this.reportVaultLoadFailure();
|
|
2384
|
-
throw error;
|
|
2385
|
-
}
|
|
2386
|
-
this.postTheme();
|
|
2387
|
-
this.postSubmitGate();
|
|
2388
|
-
this.postCardFieldOrder();
|
|
2389
|
-
addBreadcrumb(vaultLog("vault.widget.mounted", "vault_mount"));
|
|
2390
|
-
this.telemetryReporter?.log({
|
|
2391
|
-
name: "vault.widget.mounted",
|
|
2392
|
-
stage: "vault_mount",
|
|
2393
|
-
provider: "pcivault",
|
|
2394
|
-
paymentMethodCategory: "card"
|
|
2395
|
-
});
|
|
2396
|
-
this.emit("ready", { sessionId: this.config.sessionId });
|
|
2397
|
-
}
|
|
2398
|
-
reportVaultLoadFailure() {
|
|
2399
|
-
this.telemetryReporter?.error({
|
|
2400
|
-
errorCode: "VAULT_LOAD_FAILED",
|
|
2401
|
-
stage: "vault_mount",
|
|
2402
|
-
provider: "pcivault",
|
|
2403
|
-
paymentMethodCategory: "card"
|
|
2404
|
-
});
|
|
2405
|
-
}
|
|
2406
|
-
on(event, handler) {
|
|
2407
|
-
let set = this.listeners.get(event);
|
|
2408
|
-
if (!set) {
|
|
2409
|
-
set = /* @__PURE__ */ new Set();
|
|
2410
|
-
this.listeners.set(event, set);
|
|
2411
|
-
}
|
|
2412
|
-
set.add(handler);
|
|
2413
|
-
return () => {
|
|
2414
|
-
this.listeners.get(event)?.delete(handler);
|
|
2415
|
-
};
|
|
2416
|
-
}
|
|
2417
|
-
unmount() {
|
|
2418
|
-
this.hideActionRequiredOverlay();
|
|
2419
|
-
if (this.messageHandler) {
|
|
2420
|
-
window.removeEventListener("message", this.messageHandler);
|
|
2421
|
-
this.messageHandler = null;
|
|
2422
|
-
}
|
|
2423
|
-
if (this.container) {
|
|
2424
|
-
this.container.replaceChildren();
|
|
2425
|
-
this.container = null;
|
|
2426
|
-
}
|
|
2427
|
-
this.messageToken = null;
|
|
2428
|
-
this.expectedOrigin = null;
|
|
2429
|
-
if (this.ownsTelemetryReporter) {
|
|
2430
|
-
this.telemetryReporter?.destroy();
|
|
2431
|
-
this.telemetryReporter = void 0;
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
// ── internals ──
|
|
2435
|
-
/**
|
|
2436
|
-
* Inject the server-rendered widget HTML. `innerHTML` does not execute
|
|
2437
|
-
* embedded `<script>` tags, so each script node is replaced with a freshly
|
|
2438
|
-
* created element that the browser will load and run (this is what boots the
|
|
2439
|
-
* PCIVault form bundle against the `data-flopay-config` container).
|
|
2440
|
-
*/
|
|
2441
|
-
injectWidget(container, html) {
|
|
2442
|
-
container.innerHTML = html;
|
|
2443
|
-
const scripts = Array.from(container.querySelectorAll("script"));
|
|
2444
|
-
for (const oldScript of scripts) {
|
|
2445
|
-
const script = document.createElement("script");
|
|
2446
|
-
for (const attr of Array.from(oldScript.attributes)) {
|
|
2447
|
-
script.setAttribute(attr.name, attr.value);
|
|
2448
|
-
}
|
|
2449
|
-
script.text = oldScript.text;
|
|
2450
|
-
oldScript.replaceWith(script);
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
attachMessageListener() {
|
|
2454
|
-
if (this.messageHandler) return;
|
|
2455
|
-
const handler = (event) => {
|
|
2456
|
-
if (this.expectedOrigin && event.origin !== this.expectedOrigin) return;
|
|
2457
|
-
const data = event.data;
|
|
2458
|
-
if (isVaultResizeMessage(data)) {
|
|
2459
|
-
if (this.messageToken && data.messageToken !== this.messageToken) return;
|
|
2460
|
-
this.applyHeight(data.height);
|
|
2461
|
-
return;
|
|
2462
|
-
}
|
|
2463
|
-
if (isVaultValidationMessage(data)) {
|
|
2464
|
-
if (this.messageToken && data.messageToken !== this.messageToken) return;
|
|
2465
|
-
const text = data.messages.filter((m) => typeof m === "string" && m.trim()).join(" ");
|
|
2466
|
-
this.emit("validation", { sessionId: this.config.sessionId, message: text || void 0 });
|
|
2467
|
-
return;
|
|
2468
|
-
}
|
|
2469
|
-
if (!isVaultResultMessage(data)) return;
|
|
2470
|
-
if (this.messageToken && data.messageToken !== this.messageToken) return;
|
|
2471
|
-
const boundSession = this.config.sessionId;
|
|
2472
|
-
const incomingSession = typeof data.sessionId === "string" ? data.sessionId : void 0;
|
|
2473
|
-
if (boundSession && incomingSession && incomingSession !== boundSession) {
|
|
2474
|
-
return;
|
|
2475
|
-
}
|
|
2476
|
-
if ((data.type === "complete" || data.type === "decline") && boundSession && incomingSession !== boundSession) {
|
|
2477
|
-
return;
|
|
2478
|
-
}
|
|
2479
|
-
const outcome = {
|
|
2480
|
-
sessionId: data.sessionId ?? this.config.sessionId,
|
|
2481
|
-
intentId: data.intentId,
|
|
2482
|
-
declineReason: data.declineReason,
|
|
2483
|
-
message: data.message,
|
|
2484
|
-
nextActionRedirectUrl: data.nextActionRedirectUrl
|
|
2485
|
-
};
|
|
2486
|
-
if (data.type === "submitting") {
|
|
2487
|
-
this.submissionStarted = true;
|
|
2488
|
-
this.submissionStartedAt = this.telemetryReporter?.now?.() ?? telemetryNow();
|
|
2489
|
-
}
|
|
2490
|
-
addBreadcrumb(vaultOutcomeBreadcrumb(data.type, this.submissionStarted));
|
|
2491
|
-
this.reportOutcome(data.type);
|
|
2492
|
-
if (data.type === "ready") {
|
|
2493
|
-
this.postTheme();
|
|
2494
|
-
this.postSubmitGate();
|
|
2495
|
-
this.postCardFieldOrder();
|
|
2496
|
-
}
|
|
2497
|
-
if (data.type === "action_required" && data.nextActionRedirectUrl) {
|
|
2498
|
-
this.showActionRequiredOverlay(data.nextActionRedirectUrl);
|
|
2499
|
-
}
|
|
2500
|
-
if (data.type === "complete" || data.type === "decline" || data.type === "error" || data.type === "submitting") {
|
|
2501
|
-
this.hideActionRequiredOverlay();
|
|
2502
|
-
}
|
|
2503
|
-
this.emit(data.type, outcome);
|
|
2504
|
-
};
|
|
2505
|
-
this.messageHandler = handler;
|
|
2506
|
-
window.addEventListener("message", handler);
|
|
2507
|
-
}
|
|
2508
|
-
reportOutcome(type) {
|
|
2509
|
-
const reporter = this.telemetryReporter;
|
|
2510
|
-
if (!reporter) return;
|
|
2511
|
-
if (type === "ready" && !this.vaultReadyReported) {
|
|
2512
|
-
this.vaultReadyReported = true;
|
|
2513
|
-
reporter.performance({
|
|
2514
|
-
stage: "vault_ready",
|
|
2515
|
-
durationMs: Math.max(0, reporter.now() - this.captureRequestedAt),
|
|
2516
|
-
durationMode: "machine",
|
|
2517
|
-
provider: "pcivault",
|
|
2518
|
-
paymentMethodCategory: "card"
|
|
2519
|
-
});
|
|
2520
|
-
}
|
|
2521
|
-
if (type === "complete" || type === "decline") {
|
|
2522
|
-
reporter.log({
|
|
2523
|
-
name: "vault.terminal",
|
|
2524
|
-
stage: "completion",
|
|
2525
|
-
provider: "pcivault",
|
|
2526
|
-
paymentMethodCategory: "card"
|
|
2527
|
-
});
|
|
2528
|
-
reporter.terminal({
|
|
2529
|
-
outcome: type === "complete" ? "payment_succeeded" : "payment_declined",
|
|
2530
|
-
provider: "pcivault",
|
|
2531
|
-
paymentMethodCategory: "card"
|
|
2532
|
-
});
|
|
2533
|
-
return;
|
|
2534
|
-
}
|
|
2535
|
-
if (type === "error") {
|
|
2536
|
-
const classification = vaultErrorClassification(this.submissionStarted);
|
|
2537
|
-
reporter.log({
|
|
2538
|
-
name: "vault.terminal",
|
|
2539
|
-
stage: classification.stage,
|
|
2540
|
-
provider: "pcivault",
|
|
2541
|
-
paymentMethodCategory: "card"
|
|
2542
|
-
});
|
|
2543
|
-
reporter.error({
|
|
2544
|
-
...classification,
|
|
2545
|
-
provider: "pcivault",
|
|
2546
|
-
paymentMethodCategory: "card"
|
|
2547
|
-
});
|
|
2548
|
-
return;
|
|
2549
|
-
}
|
|
2550
|
-
if (type === "action_required") {
|
|
2551
|
-
reporter.log({
|
|
2552
|
-
name: "vault.action.required",
|
|
2553
|
-
stage: "three_ds_handoff",
|
|
2554
|
-
provider: "pcivault",
|
|
2555
|
-
paymentMethodCategory: "card"
|
|
2556
|
-
});
|
|
2557
|
-
reporter.log({
|
|
2558
|
-
name: "vault.three_ds.handoff",
|
|
2559
|
-
stage: "three_ds_handoff",
|
|
2560
|
-
provider: "pcivault",
|
|
2561
|
-
paymentMethodCategory: "card"
|
|
2562
|
-
});
|
|
2563
|
-
const submissionStartedAt = this.submissionStartedAt;
|
|
2564
|
-
this.submissionStartedAt = null;
|
|
2565
|
-
if (submissionStartedAt !== null) {
|
|
2566
|
-
reporter.performance({
|
|
2567
|
-
stage: "three_ds_handoff",
|
|
2568
|
-
durationMs: Math.max(0, reporter.now() - submissionStartedAt),
|
|
2569
|
-
durationMode: "machine",
|
|
2570
|
-
provider: "pcivault",
|
|
2571
|
-
paymentMethodCategory: "card"
|
|
2572
|
-
});
|
|
2573
|
-
}
|
|
2574
|
-
reporter.terminal({
|
|
2575
|
-
outcome: "action_required",
|
|
2576
|
-
stage: "three_ds_handoff",
|
|
2577
|
-
provider: "pcivault",
|
|
2578
|
-
paymentMethodCategory: "card"
|
|
2579
|
-
});
|
|
2580
|
-
return;
|
|
2581
|
-
}
|
|
2582
|
-
const [name, stage] = VAULT_NON_TERMINAL_OUTCOME_LOGS[type];
|
|
2583
|
-
reporter.log({
|
|
2584
|
-
name,
|
|
2585
|
-
stage,
|
|
2586
|
-
provider: "pcivault",
|
|
2587
|
-
paymentMethodCategory: "card"
|
|
2588
|
-
});
|
|
2589
|
-
}
|
|
2590
|
-
/**
|
|
2591
|
-
* Push merchant theme colors into the hosted widget (live). The host calls
|
|
2592
|
-
* this on a runtime theme switch; the widget applies them to its CSS variables
|
|
2593
|
-
* without a remount. Stores the latest theme so `ready` can re-push it.
|
|
2594
|
-
*/
|
|
2595
|
-
applyTheme(theme) {
|
|
2596
|
-
this.theme = theme;
|
|
2597
|
-
this.postTheme();
|
|
2598
|
-
}
|
|
2599
|
-
/** postMessage the current theme to the widget's (cross-origin) document. */
|
|
2600
|
-
postTheme() {
|
|
2601
|
-
if (!this.theme || !this.container) return;
|
|
2602
|
-
const iframe = this.container.querySelector("iframe");
|
|
2603
|
-
const target = iframe?.contentWindow;
|
|
2604
|
-
if (!target) return;
|
|
2605
|
-
try {
|
|
2606
|
-
target.postMessage({ source: "flopay-vault-host", type: "theme", theme: this.theme }, "*");
|
|
2607
|
-
} catch {
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
/**
|
|
2611
|
-
* Gate the widget's submit from the host. When `blocked`, the widget cancels
|
|
2612
|
-
* its next submit and emits `'blocked'` instead of `'submitting'` so the host
|
|
2613
|
-
* can validate merchant-DOM fields (AVS) first. Stored so `ready` re-pushes it.
|
|
2614
|
-
*/
|
|
2615
|
-
setSubmitGate(blocked) {
|
|
2616
|
-
this.submitGateBlocked = blocked;
|
|
2617
|
-
this.postSubmitGate();
|
|
2618
|
-
}
|
|
2619
|
-
/** postMessage the current submit-gate state to the widget's document. */
|
|
2620
|
-
postSubmitGate() {
|
|
2621
|
-
if (!this.container) return;
|
|
2622
|
-
const iframe = this.container.querySelector("iframe");
|
|
2623
|
-
const target = iframe?.contentWindow;
|
|
2624
|
-
if (!target) return;
|
|
2625
|
-
try {
|
|
2626
|
-
target.postMessage(
|
|
2627
|
-
{ source: "flopay-vault-host", type: "gate", blocked: this.submitGateBlocked },
|
|
2628
|
-
"*"
|
|
2629
|
-
);
|
|
2630
|
-
} catch {
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
/**
|
|
2634
|
-
* Push the card-field order + autofocus directive into the widget (live). The
|
|
2635
|
-
* widget re-sequences its rows (DOM order, so tab order follows) and focuses
|
|
2636
|
-
* its first field unless `autoFocus` is false. Stored so `ready` re-pushes it.
|
|
2637
|
-
*/
|
|
2638
|
-
setCardFieldOrder(order, autoFocus) {
|
|
2639
|
-
this.cardFieldOrder = order;
|
|
2640
|
-
this.cardAutoFocus = autoFocus;
|
|
2641
|
-
this.postCardFieldOrder();
|
|
2642
|
-
}
|
|
2643
|
-
/** postMessage the current field order + autofocus to the widget's document. */
|
|
2644
|
-
postCardFieldOrder() {
|
|
2645
|
-
if (!this.container) return;
|
|
2646
|
-
const iframe = this.container.querySelector("iframe");
|
|
2647
|
-
const target = iframe?.contentWindow;
|
|
2648
|
-
if (!target) return;
|
|
2649
|
-
try {
|
|
2650
|
-
target.postMessage(
|
|
2651
|
-
{
|
|
2652
|
-
source: "flopay-vault-host",
|
|
2653
|
-
type: "fieldOrder",
|
|
2654
|
-
order: this.cardFieldOrder,
|
|
2655
|
-
autoFocus: this.cardAutoFocus
|
|
2656
|
-
},
|
|
2657
|
-
"*"
|
|
2658
|
-
);
|
|
2659
|
-
} catch {
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
emit(event, payload) {
|
|
2663
|
-
for (const handler of this.listeners.get(event) ?? []) {
|
|
2664
|
-
handler(payload);
|
|
2665
|
-
}
|
|
2666
|
-
}
|
|
2667
|
-
/**
|
|
2668
|
-
* Render the provider-hosted verification challenge (e.g. Stripe 3DS-2) in a
|
|
2669
|
-
* full-page overlay at the PARENT page level. The widget's inline-iframe
|
|
2670
|
-
* approach is unusable because the SDK's processing backdrop sits above the
|
|
2671
|
-
* vault iframe, hiding any challenge mounted inside it — by lifting the
|
|
2672
|
-
* iframe to the host page the adapter can give it a z-index that wins.
|
|
2673
|
-
*
|
|
2674
|
-
* The overlay tears down on the next terminal outcome
|
|
2675
|
-
* (`complete`/`decline`/`error`) or when the buyer closes it via the backdrop
|
|
2676
|
-
* close button. Closing manually is a soft abandon — the next `/status` poll
|
|
2677
|
-
* either reveals a real outcome (the challenge completed via the issuer's
|
|
2678
|
-
* own redirect to `/vault/3ds/return`, which posts back into the widget) or
|
|
2679
|
-
* surfaces `requires_action` again so the host can decide what to do.
|
|
2680
|
-
*/
|
|
2681
|
-
showActionRequiredOverlay(challengeUrl) {
|
|
2682
|
-
if (typeof document === "undefined") return;
|
|
2683
|
-
if (this.actionOverlay) {
|
|
2684
|
-
const existingIframe = this.actionOverlay.querySelector("iframe");
|
|
2685
|
-
if (existingIframe instanceof HTMLIFrameElement) {
|
|
2686
|
-
existingIframe.src = challengeUrl;
|
|
2687
|
-
}
|
|
2688
|
-
return;
|
|
2689
|
-
}
|
|
2690
|
-
const backdrop = document.createElement("div");
|
|
2691
|
-
backdrop.setAttribute("data-flopay-action-required", "1");
|
|
2692
|
-
backdrop.style.cssText = [
|
|
2693
|
-
"position:fixed",
|
|
2694
|
-
"inset:0",
|
|
2695
|
-
// Maximum signed 32-bit z-index; the SDK's own processing backdrop sits
|
|
2696
|
-
// well below this so the challenge is visible and interactive.
|
|
2697
|
-
"z-index:2147483647",
|
|
2698
|
-
"background:rgba(15,23,42,0.6)",
|
|
2699
|
-
"display:flex",
|
|
2700
|
-
"align-items:center",
|
|
2701
|
-
"justify-content:center",
|
|
2702
|
-
"padding:16px"
|
|
2703
|
-
].join(";");
|
|
2704
|
-
const frame = document.createElement("iframe");
|
|
2705
|
-
frame.setAttribute("title", "Card authentication");
|
|
2706
|
-
frame.setAttribute("allow", "payment");
|
|
2707
|
-
frame.style.cssText = [
|
|
2708
|
-
"width:min(100%,460px)",
|
|
2709
|
-
"height:min(100%,640px)",
|
|
2710
|
-
"border:0",
|
|
2711
|
-
"border-radius:12px",
|
|
2712
|
-
"background:#fff",
|
|
2713
|
-
"box-shadow:0 12px 30px rgba(0,0,0,0.35)"
|
|
2714
|
-
].join(";");
|
|
2715
|
-
frame.src = challengeUrl;
|
|
2716
|
-
backdrop.appendChild(frame);
|
|
2717
|
-
const closeButton = document.createElement("button");
|
|
2718
|
-
closeButton.type = "button";
|
|
2719
|
-
closeButton.setAttribute("aria-label", "Close card authentication");
|
|
2720
|
-
closeButton.textContent = "\xD7";
|
|
2721
|
-
closeButton.style.cssText = [
|
|
2722
|
-
"position:fixed",
|
|
2723
|
-
"top:20px",
|
|
2724
|
-
"right:20px",
|
|
2725
|
-
"width:40px",
|
|
2726
|
-
"height:40px",
|
|
2727
|
-
"border:0",
|
|
2728
|
-
"border-radius:9999px",
|
|
2729
|
-
"background:#fff",
|
|
2730
|
-
"color:#0f172a",
|
|
2731
|
-
"font-size:28px",
|
|
2732
|
-
"line-height:40px",
|
|
2733
|
-
"cursor:pointer",
|
|
2734
|
-
"box-shadow:0 4px 14px rgba(0,0,0,0.25)"
|
|
2735
|
-
].join(";");
|
|
2736
|
-
closeButton.addEventListener("click", () => this.abandonActionRequiredOverlay());
|
|
2737
|
-
backdrop.appendChild(closeButton);
|
|
2738
|
-
backdrop.addEventListener("click", (event) => {
|
|
2739
|
-
if (event.target === backdrop) this.abandonActionRequiredOverlay();
|
|
2740
|
-
});
|
|
2741
|
-
const returnHandler = (event) => {
|
|
2742
|
-
if (event.source !== frame.contentWindow) return;
|
|
2743
|
-
const data = event.data;
|
|
2744
|
-
if (!data || typeof data !== "object") return;
|
|
2745
|
-
const record = data;
|
|
2746
|
-
if (record["source"] !== "flopay-vault-3ds-return") return;
|
|
2747
|
-
addBreadcrumb(vaultLog("vault.three_ds.returned", "three_ds_return"));
|
|
2748
|
-
this.telemetryReporter?.log({
|
|
2749
|
-
name: "vault.three_ds.returned",
|
|
2750
|
-
stage: "three_ds_return",
|
|
2751
|
-
provider: "pcivault",
|
|
2752
|
-
paymentMethodCategory: "card"
|
|
2753
|
-
});
|
|
2754
|
-
this.hideActionRequiredOverlay();
|
|
2755
|
-
this.postActionCompleted(record["status"]);
|
|
2756
|
-
};
|
|
2757
|
-
window.addEventListener("message", returnHandler);
|
|
2758
|
-
this.threeDsReturnHandler = returnHandler;
|
|
2759
|
-
document.body.appendChild(backdrop);
|
|
2760
|
-
this.actionOverlay = backdrop;
|
|
2761
|
-
addBreadcrumb(vaultLog("vault.three_ds.handoff", "three_ds_handoff"));
|
|
2762
|
-
}
|
|
2763
|
-
/**
|
|
2764
|
-
* Tell the vault widget that the buyer has completed (or abandoned) the
|
|
2765
|
-
* challenge. The widget responds by POSTing `/3ds/complete` — its
|
|
2766
|
-
* sub-300ms sync resolver writes the follow-up attempt row immediately,
|
|
2767
|
-
* so the next `/status` poll resolves to a terminal outcome instead of
|
|
2768
|
-
* waiting for the eventual provider webhook.
|
|
2769
|
-
*/
|
|
2770
|
-
postActionCompleted(status) {
|
|
2771
|
-
if (!this.container) return;
|
|
2772
|
-
const iframe = this.container.querySelector("iframe");
|
|
2773
|
-
const target = iframe?.contentWindow;
|
|
2774
|
-
if (!target) return;
|
|
2775
|
-
try {
|
|
2776
|
-
target.postMessage(
|
|
2777
|
-
{
|
|
2778
|
-
source: "flopay-vault-host",
|
|
2779
|
-
type: "action_completed",
|
|
2780
|
-
status: typeof status === "string" ? status : "unknown"
|
|
2781
|
-
},
|
|
2782
|
-
"*"
|
|
2783
|
-
);
|
|
2784
|
-
} catch {
|
|
2785
|
-
}
|
|
2786
|
-
}
|
|
2787
|
-
abandonActionRequiredOverlay() {
|
|
2788
|
-
if (!this.actionOverlay) return;
|
|
2789
|
-
const breadcrumb = (0, import_shared5.buildTelemetryTerminalEvent)({
|
|
2790
|
-
eventId: "77777777-7777-4777-8777-777777777777",
|
|
2791
|
-
outcome: "customer_abandoned",
|
|
2792
|
-
stage: "three_ds_handoff",
|
|
2793
|
-
sequence: 0,
|
|
2794
|
-
provider: "pcivault",
|
|
2795
|
-
paymentMethodCategory: "card"
|
|
2796
|
-
});
|
|
2797
|
-
addBreadcrumb(breadcrumb);
|
|
2798
|
-
this.telemetryReporter?.terminal({
|
|
2799
|
-
outcome: "customer_abandoned",
|
|
2800
|
-
stage: "three_ds_handoff",
|
|
2801
|
-
provider: "pcivault",
|
|
2802
|
-
paymentMethodCategory: "card"
|
|
2803
|
-
});
|
|
2804
|
-
this.hideActionRequiredOverlay();
|
|
2805
|
-
this.postActionCompleted("abandoned");
|
|
2806
|
-
}
|
|
2807
|
-
hideActionRequiredOverlay() {
|
|
2808
|
-
if (this.threeDsReturnHandler) {
|
|
2809
|
-
window.removeEventListener("message", this.threeDsReturnHandler);
|
|
2810
|
-
this.threeDsReturnHandler = null;
|
|
2811
|
-
}
|
|
2812
|
-
if (!this.actionOverlay) return;
|
|
2813
|
-
this.actionOverlay.parentNode?.removeChild(this.actionOverlay);
|
|
2814
|
-
this.actionOverlay = null;
|
|
2815
|
-
}
|
|
2816
|
-
/**
|
|
2817
|
-
* Size the hosted-widget iframe to the height reported by the form inside it.
|
|
2818
|
-
* Cross-origin iframes don't auto-size to their content, so the widget posts
|
|
2819
|
-
* its measured height and we apply it here (clamped to a sane range). This is
|
|
2820
|
-
* what lets the card form shrink/grow to fit instead of sitting at a fixed
|
|
2821
|
-
* height.
|
|
2822
|
-
*/
|
|
2823
|
-
applyHeight(height) {
|
|
2824
|
-
const iframe = this.container?.querySelector("iframe");
|
|
2825
|
-
if (!iframe) return;
|
|
2826
|
-
const clamped = Math.max(0, Math.min(Math.ceil(height), 2e3));
|
|
2827
|
-
iframe.style.height = `${clamped}px`;
|
|
2828
|
-
}
|
|
2829
|
-
};
|
|
2830
|
-
function createInstrumentedPciVaultCardCapture(config, reporter, requestedAt) {
|
|
2831
|
-
const InstrumentedCapture = PciVaultCardCapture;
|
|
2832
|
-
return new InstrumentedCapture(config, { reporter, requestedAt });
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
// src/telemetry-bridge.ts
|
|
2836
|
-
var FLOPAY_TELEMETRY_BRIDGE = /* @__PURE__ */ Symbol.for("@flopay/js.telemetry.bridge.v1");
|
|
2837
|
-
function attachFloPayTelemetryBridge(target, reporter, fallbackNow) {
|
|
2838
|
-
const now = () => reporter?.now() ?? fallbackNow();
|
|
2839
|
-
const bridge = {
|
|
2840
|
-
error: (input) => reporter?.error(input),
|
|
2841
|
-
log: (input) => reporter?.log(input),
|
|
2842
|
-
performance: (input) => reporter?.performance(input),
|
|
2843
|
-
terminal: (input) => reporter?.terminal(input),
|
|
2844
|
-
now,
|
|
2845
|
-
elapsed: (startedAt) => Math.max(0, now() - startedAt),
|
|
2846
|
-
setCheckoutContext: (context) => reporter?.setCheckoutContext(context),
|
|
2847
|
-
beginCheckout: (context = {}) => reporter?.beginCheckout(context) ?? now(),
|
|
2848
|
-
disable: () => reporter?.disable()
|
|
2849
|
-
};
|
|
2850
|
-
Object.defineProperty(target, FLOPAY_TELEMETRY_BRIDGE, {
|
|
2851
|
-
configurable: false,
|
|
2852
|
-
enumerable: false,
|
|
2853
|
-
writable: false,
|
|
2854
|
-
value: bridge
|
|
2855
|
-
});
|
|
2856
|
-
}
|
|
2857
|
-
function getFloPayTelemetryBridge(target) {
|
|
2858
|
-
return target[FLOPAY_TELEMETRY_BRIDGE];
|
|
2859
|
-
}
|
|
2860
|
-
|
|
2861
|
-
// src/flopay.ts
|
|
2862
|
-
function isExpectedDecline(error) {
|
|
2863
|
-
if (!error) return false;
|
|
2864
|
-
const code = error.code?.toLowerCase() ?? "";
|
|
2865
|
-
return Boolean(error.declineCode) || code.includes("declin");
|
|
2866
|
-
}
|
|
2867
|
-
function telemetryProvider(name) {
|
|
2868
|
-
if (name === "stripe" || name === "paypal" || name === "pcivault") return name;
|
|
2869
|
-
return "other";
|
|
2870
|
-
}
|
|
2871
|
-
var FloPay = class {
|
|
2872
|
-
constructor(provider, config, telemetryReporter) {
|
|
2873
|
-
this.currentElements = null;
|
|
2874
|
-
this.provider = provider;
|
|
2875
|
-
this.config = config;
|
|
2876
|
-
this.telemetryReporter = telemetryReporter ?? new TelemetryReporter({
|
|
2877
|
-
billingApiUrl: (0, import_shared6.resolveBillingApiUrl)(config.billingApiUrl),
|
|
2878
|
-
sdkVersion: import_shared6.SDK_VERSION,
|
|
2879
|
-
enabled: config.telemetry !== false
|
|
2880
|
-
});
|
|
2881
|
-
attachFloPayTelemetryBridge(this, this.telemetryReporter, telemetryNow);
|
|
2882
|
-
}
|
|
2883
|
-
now() {
|
|
2884
|
-
return this.telemetryReporter?.now?.() ?? telemetryNow();
|
|
2885
|
-
}
|
|
2886
|
-
/**
|
|
2887
|
-
* Creates a new `FloPayElements` group for mounting payment fields.
|
|
2888
|
-
*
|
|
2889
|
-
* Only one elements group is active at a time. Creating a new one
|
|
2890
|
-
* destroys the previous group.
|
|
2891
|
-
*/
|
|
2892
|
-
elements(options) {
|
|
2893
|
-
if (this.currentElements) {
|
|
2894
|
-
this.currentElements.destroy();
|
|
2895
|
-
}
|
|
2896
|
-
this.currentElements = new FloPayElements(this.provider, {
|
|
2897
|
-
appearance: this.config.appearance,
|
|
2898
|
-
...options
|
|
2899
|
-
});
|
|
2900
|
-
return this.currentElements;
|
|
2901
|
-
}
|
|
2902
|
-
/** Submit elements for validation. */
|
|
2903
|
-
async submitElements() {
|
|
2904
|
-
return this.provider.submitElements();
|
|
2905
|
-
}
|
|
2906
|
-
/**
|
|
2907
|
-
* Create a {@link CardCaptureAdapter} for collecting card details through the
|
|
2908
|
-
* backend-rendered hosted vault PCI widget (TeamFloPay/backend#823).
|
|
2909
|
-
*
|
|
2910
|
-
* The returned adapter injects the server-supplied widget HTML (the session's
|
|
2911
|
-
* {@link CheckoutSession.vault} block, or one fetched via
|
|
2912
|
-
* `PaymentAPI.getVaultCapture`) and relays the widget's terminal outcome. The
|
|
2913
|
-
* backend owns tokenization, the PaymentIntent, 3DS, and fulfilment — no
|
|
2914
|
-
* Stripe.js is involved on the card path and PCI-sensitive fields never enter
|
|
2915
|
-
* the SDK runtime.
|
|
2916
|
-
*/
|
|
2917
|
-
cardCapture(options) {
|
|
2918
|
-
const requestedAt = this.now();
|
|
2919
|
-
this.telemetryReporter?.log({
|
|
2920
|
-
name: "vault.capture.requested",
|
|
2921
|
-
stage: "vault_request",
|
|
2922
|
-
provider: "pcivault",
|
|
2923
|
-
paymentMethodCategory: "card"
|
|
2924
|
-
});
|
|
2925
|
-
if (this.telemetryReporter) {
|
|
2926
|
-
return createInstrumentedPciVaultCardCapture(
|
|
2927
|
-
{ sessionId: options?.sessionId },
|
|
2928
|
-
this.telemetryReporter,
|
|
2929
|
-
requestedAt
|
|
2930
|
-
);
|
|
2931
|
-
}
|
|
2932
|
-
return new PciVaultCardCapture({
|
|
2933
|
-
sessionId: options?.sessionId,
|
|
2934
|
-
telemetry: false
|
|
2935
|
-
});
|
|
2936
|
-
}
|
|
2937
|
-
/** Confirm a PayPal payment: create intent via billing API → confirm → redirect if needed. */
|
|
2938
|
-
async confirmPayPalPayment(params) {
|
|
2939
|
-
const startedAt = this.now();
|
|
2940
|
-
this.telemetryReporter?.log({
|
|
2941
|
-
name: "payment.method.selected",
|
|
2942
|
-
stage: "processing",
|
|
2943
|
-
provider: "paypal",
|
|
2944
|
-
paymentMethodCategory: "paypal"
|
|
2945
|
-
});
|
|
2946
|
-
this.telemetryReporter?.log({
|
|
2947
|
-
name: "payment.intent.started",
|
|
2948
|
-
stage: "processing",
|
|
2949
|
-
provider: "paypal",
|
|
2950
|
-
paymentMethodCategory: "paypal",
|
|
2951
|
-
requestCategory: "intent_create"
|
|
2952
|
-
});
|
|
2953
|
-
try {
|
|
2954
|
-
const result = await this.provider.confirmPayPalPayment(params);
|
|
2955
|
-
this.telemetryReporter?.performance({
|
|
2956
|
-
stage: "processing",
|
|
2957
|
-
durationMs: this.now() - startedAt,
|
|
2958
|
-
durationMode: "machine",
|
|
2959
|
-
provider: "paypal",
|
|
2960
|
-
paymentMethodCategory: "paypal"
|
|
2961
|
-
});
|
|
2962
|
-
if (!result.error) {
|
|
2963
|
-
this.telemetryReporter?.log({
|
|
2964
|
-
name: "payment.intent.completed",
|
|
2965
|
-
stage: "processing",
|
|
2966
|
-
provider: "paypal",
|
|
2967
|
-
paymentMethodCategory: "paypal",
|
|
2968
|
-
requestCategory: "intent_create",
|
|
2969
|
-
statusClass: "2xx"
|
|
2970
|
-
});
|
|
2971
|
-
}
|
|
2972
|
-
if (result.status === "requires_action") {
|
|
2973
|
-
this.telemetryReporter?.log({
|
|
2974
|
-
name: "provider.redirect.started",
|
|
2975
|
-
stage: "redirect",
|
|
2976
|
-
provider: "paypal",
|
|
2977
|
-
paymentMethodCategory: "paypal"
|
|
2978
|
-
});
|
|
2979
|
-
this.telemetryReporter?.terminal({
|
|
2980
|
-
outcome: "action_required",
|
|
2981
|
-
stage: "redirect",
|
|
2982
|
-
provider: "paypal",
|
|
2983
|
-
paymentMethodCategory: "paypal"
|
|
2984
|
-
});
|
|
2985
|
-
} else if (result.status === "succeeded") {
|
|
2986
|
-
this.telemetryReporter?.terminal({
|
|
2987
|
-
outcome: "payment_succeeded",
|
|
2988
|
-
provider: "paypal",
|
|
2989
|
-
paymentMethodCategory: "paypal"
|
|
2990
|
-
});
|
|
2991
|
-
} else if (result.status !== "processing") {
|
|
2992
|
-
if (isExpectedDecline(result.error)) {
|
|
2993
|
-
this.telemetryReporter?.terminal({
|
|
2994
|
-
outcome: "payment_declined",
|
|
2995
|
-
provider: "paypal",
|
|
2996
|
-
paymentMethodCategory: "paypal"
|
|
2997
|
-
});
|
|
2998
|
-
} else if (result.error?.type === "validation_error") {
|
|
2999
|
-
this.telemetryReporter?.terminal({
|
|
3000
|
-
outcome: "validation_rejected",
|
|
3001
|
-
provider: "paypal",
|
|
3002
|
-
paymentMethodCategory: "paypal"
|
|
3003
|
-
});
|
|
3004
|
-
} else if (result.error) {
|
|
3005
|
-
this.telemetryReporter?.error({
|
|
3006
|
-
errorCode: "PAYMENT_PROCESSING_FAILED",
|
|
3007
|
-
stage: "processing",
|
|
3008
|
-
provider: "paypal",
|
|
3009
|
-
paymentMethodCategory: "paypal",
|
|
3010
|
-
requestCategory: "intent_create"
|
|
3011
|
-
});
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
return result;
|
|
3015
|
-
} catch (error) {
|
|
3016
|
-
this.telemetryReporter?.performance({
|
|
3017
|
-
stage: "processing",
|
|
3018
|
-
durationMs: this.now() - startedAt,
|
|
3019
|
-
durationMode: "machine",
|
|
3020
|
-
provider: "paypal",
|
|
3021
|
-
paymentMethodCategory: "paypal"
|
|
3022
|
-
});
|
|
3023
|
-
this.telemetryReporter?.error({
|
|
3024
|
-
errorCode: "NETWORK_REQUEST_FAILED",
|
|
3025
|
-
stage: "processing",
|
|
3026
|
-
provider: "paypal",
|
|
3027
|
-
paymentMethodCategory: "paypal",
|
|
3028
|
-
requestCategory: "intent_create",
|
|
3029
|
-
statusClass: "network_error"
|
|
3030
|
-
});
|
|
3031
|
-
throw error;
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
/** Resume a PayPal payment after redirect return. Returns null if no PayPal params in URL. */
|
|
3035
|
-
async resumePayPalPayment() {
|
|
3036
|
-
const startedAt = this.now();
|
|
3037
|
-
try {
|
|
3038
|
-
const result = await this.provider.resumePayPalPayment();
|
|
3039
|
-
if (result === null) return null;
|
|
3040
|
-
this.telemetryReporter?.log({
|
|
3041
|
-
name: "provider.redirect.resumed",
|
|
3042
|
-
stage: "redirect_resume",
|
|
3043
|
-
provider: "paypal",
|
|
3044
|
-
paymentMethodCategory: "paypal"
|
|
3045
|
-
});
|
|
3046
|
-
this.telemetryReporter?.performance({
|
|
3047
|
-
stage: "redirect_resume",
|
|
3048
|
-
durationMs: this.now() - startedAt,
|
|
3049
|
-
durationMode: "machine",
|
|
3050
|
-
provider: "paypal",
|
|
3051
|
-
paymentMethodCategory: "paypal"
|
|
3052
|
-
});
|
|
3053
|
-
if (result.status === "succeeded") {
|
|
3054
|
-
this.telemetryReporter?.terminal({
|
|
3055
|
-
outcome: "payment_succeeded",
|
|
3056
|
-
provider: "paypal",
|
|
3057
|
-
paymentMethodCategory: "paypal"
|
|
3058
|
-
});
|
|
3059
|
-
} else if (isExpectedDecline(result.error)) {
|
|
3060
|
-
this.telemetryReporter?.terminal({
|
|
3061
|
-
outcome: "payment_declined",
|
|
3062
|
-
provider: "paypal",
|
|
3063
|
-
paymentMethodCategory: "paypal"
|
|
3064
|
-
});
|
|
3065
|
-
} else if (result.error?.type === "validation_error") {
|
|
3066
|
-
this.telemetryReporter?.terminal({
|
|
3067
|
-
outcome: "validation_rejected",
|
|
3068
|
-
provider: "paypal",
|
|
3069
|
-
paymentMethodCategory: "paypal"
|
|
3070
|
-
});
|
|
3071
|
-
} else if (result.error) {
|
|
3072
|
-
this.telemetryReporter?.error({
|
|
3073
|
-
errorCode: "REDIRECT_RESUME_FAILED",
|
|
3074
|
-
stage: "redirect_resume",
|
|
3075
|
-
provider: "paypal",
|
|
3076
|
-
paymentMethodCategory: "paypal"
|
|
3077
|
-
});
|
|
3078
|
-
}
|
|
3079
|
-
return result;
|
|
3080
|
-
} catch (error) {
|
|
3081
|
-
this.telemetryReporter?.performance({
|
|
3082
|
-
stage: "redirect_resume",
|
|
3083
|
-
durationMs: this.now() - startedAt,
|
|
3084
|
-
durationMode: "machine",
|
|
3085
|
-
provider: "paypal",
|
|
3086
|
-
paymentMethodCategory: "paypal"
|
|
3087
|
-
});
|
|
3088
|
-
this.telemetryReporter?.error({
|
|
3089
|
-
errorCode: "REDIRECT_RESUME_FAILED",
|
|
3090
|
-
stage: "redirect_resume",
|
|
3091
|
-
provider: "paypal",
|
|
3092
|
-
paymentMethodCategory: "paypal"
|
|
3093
|
-
});
|
|
3094
|
-
throw error;
|
|
3095
|
-
}
|
|
3096
|
-
}
|
|
3097
|
-
/** Confirms a non-card wallet/APM payment using the mounted PaymentElement. */
|
|
3098
|
-
async confirmPayment(params) {
|
|
3099
|
-
if (params.paymentMethodCategory !== "wallet" && params.paymentMethodCategory !== "apm" || !params.paymentMethodType?.trim() || params.paymentMethodType.trim().toLowerCase() === "card") {
|
|
3100
|
-
throw new import_shared6.FloPayError(
|
|
3101
|
-
"A supported non-card payment method is required.",
|
|
3102
|
-
"validation_error",
|
|
3103
|
-
{ param: "paymentMethodType" }
|
|
3104
|
-
);
|
|
3105
|
-
}
|
|
3106
|
-
const started = this.now();
|
|
3107
|
-
const provider = telemetryProvider(this.provider.name);
|
|
3108
|
-
this.telemetryReporter?.log({
|
|
3109
|
-
name: "payment.processing.started",
|
|
3110
|
-
stage: "processing",
|
|
3111
|
-
provider,
|
|
3112
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3113
|
-
});
|
|
3114
|
-
try {
|
|
3115
|
-
const result = await this.provider.confirmPayment(params);
|
|
3116
|
-
const durationMs = this.now() - started;
|
|
3117
|
-
this.telemetryReporter?.performance({
|
|
3118
|
-
stage: "processing",
|
|
3119
|
-
durationMs,
|
|
3120
|
-
durationMode: "machine",
|
|
3121
|
-
provider,
|
|
3122
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3123
|
-
});
|
|
3124
|
-
if (result.status === "succeeded") {
|
|
3125
|
-
this.telemetryReporter?.terminal({
|
|
3126
|
-
outcome: "payment_succeeded",
|
|
3127
|
-
provider,
|
|
3128
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3129
|
-
});
|
|
3130
|
-
} else if (isExpectedDecline(result.error)) {
|
|
3131
|
-
this.telemetryReporter?.terminal({
|
|
3132
|
-
outcome: "payment_declined",
|
|
3133
|
-
provider,
|
|
3134
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3135
|
-
});
|
|
3136
|
-
} else if (result.error?.type === "validation_error") {
|
|
3137
|
-
this.telemetryReporter?.terminal({
|
|
3138
|
-
outcome: "validation_rejected",
|
|
3139
|
-
provider,
|
|
3140
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3141
|
-
});
|
|
3142
|
-
} else if (result.status === "requires_action") {
|
|
3143
|
-
this.telemetryReporter?.terminal({
|
|
3144
|
-
outcome: "action_required",
|
|
3145
|
-
stage: "three_ds_handoff",
|
|
3146
|
-
provider,
|
|
3147
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3148
|
-
});
|
|
3149
|
-
} else if (result.status === "failed" && result.error) {
|
|
3150
|
-
this.telemetryReporter?.error({
|
|
3151
|
-
errorCode: "PAYMENT_PROCESSING_FAILED",
|
|
3152
|
-
stage: "processing",
|
|
3153
|
-
provider,
|
|
3154
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3155
|
-
});
|
|
3156
|
-
}
|
|
3157
|
-
if (result.status === "succeeded" || result.status === "failed") {
|
|
3158
|
-
this.telemetryReporter?.log({
|
|
3159
|
-
name: "payment.processing.completed",
|
|
3160
|
-
stage: "processing",
|
|
3161
|
-
provider,
|
|
3162
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3163
|
-
});
|
|
3164
|
-
} else {
|
|
3165
|
-
this.telemetryReporter?.log({
|
|
3166
|
-
name: "operation.state_transition",
|
|
3167
|
-
stage: result.status === "requires_action" ? "three_ds_handoff" : "processing",
|
|
3168
|
-
provider,
|
|
3169
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3170
|
-
});
|
|
3171
|
-
}
|
|
3172
|
-
return result;
|
|
3173
|
-
} catch (error) {
|
|
3174
|
-
this.telemetryReporter?.performance({
|
|
3175
|
-
stage: "processing",
|
|
3176
|
-
durationMs: this.now() - started,
|
|
3177
|
-
durationMode: "machine",
|
|
3178
|
-
provider,
|
|
3179
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3180
|
-
});
|
|
3181
|
-
this.telemetryReporter?.error({
|
|
3182
|
-
errorCode: "PAYMENT_PROCESSING_FAILED",
|
|
3183
|
-
stage: "processing",
|
|
3184
|
-
provider,
|
|
3185
|
-
paymentMethodCategory: params.paymentMethodCategory
|
|
3186
|
-
});
|
|
3187
|
-
throw error;
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
/**
|
|
3191
|
-
* Retrieves a checkout session by ID via the billing API.
|
|
3192
|
-
*
|
|
3193
|
-
* Returns the normalized `CheckoutSession` with amount, currency,
|
|
3194
|
-
* customer data, and status.
|
|
3195
|
-
*
|
|
3196
|
-
* Requires `billingApiUrl` to be set — either via `loadFloPay(key, { billingApiUrl })`
|
|
3197
|
-
* or passed directly as the second argument.
|
|
3198
|
-
*/
|
|
3199
|
-
async retrieveSession(sessionId, billingApiUrl) {
|
|
3200
|
-
if (!sessionId) {
|
|
3201
|
-
throw new import_shared6.FloPayError(
|
|
3202
|
-
"sessionId is required to retrieve a session.",
|
|
3203
|
-
"validation_error",
|
|
3204
|
-
{ param: "sessionId" }
|
|
3205
|
-
);
|
|
3206
|
-
}
|
|
3207
|
-
const unified = await this.retrieveUnifiedSession(sessionId, billingApiUrl);
|
|
3208
|
-
if (!unified.data.session) {
|
|
3209
|
-
throw new import_shared6.FloPayError("Session not found", "api_error");
|
|
3210
|
-
}
|
|
3211
|
-
return unified.data.session;
|
|
3212
|
-
}
|
|
3213
|
-
/**
|
|
3214
|
-
* Retrieves and normalizes a checkout session, including provider-specific
|
|
3215
|
-
* data (Stripe clientSecret/publishableKey, Chargebee site, etc.).
|
|
3216
|
-
*
|
|
3217
|
-
* The billing API URL is resolved from: explicit param → `loadFloPay()` config
|
|
3218
|
-
* → `NEXT_PUBLIC_FLOPAY_ENV` env var → `configureFlopay()` → staging fallback.
|
|
3219
|
-
*/
|
|
3220
|
-
async retrieveUnifiedSession(sessionId, billingApiUrl) {
|
|
3221
|
-
if (!sessionId) {
|
|
3222
|
-
throw new import_shared6.FloPayError(
|
|
3223
|
-
"sessionId is required.",
|
|
3224
|
-
"validation_error",
|
|
3225
|
-
{ param: "sessionId" }
|
|
3226
|
-
);
|
|
3227
|
-
}
|
|
3228
|
-
const apiUrl = (0, import_shared6.resolveBillingApiUrl)(billingApiUrl ?? this.config.billingApiUrl);
|
|
3229
|
-
const started = this.now();
|
|
3230
|
-
this.telemetryReporter?.log({
|
|
3231
|
-
name: "session.read.started",
|
|
3232
|
-
stage: "session_read",
|
|
3233
|
-
requestCategory: "session_read"
|
|
3234
|
-
});
|
|
3235
|
-
let firstByteDuration;
|
|
3236
|
-
const api = createInstrumentedPaymentAPI(apiUrl, {
|
|
3237
|
-
now: () => this.telemetryReporter?.now() ?? telemetryNow(),
|
|
3238
|
-
onFirstByte: (durationMs) => {
|
|
3239
|
-
firstByteDuration = durationMs;
|
|
3240
|
-
},
|
|
3241
|
-
onRetry: (requestCategory, attempt) => {
|
|
3242
|
-
this.telemetryReporter?.log({
|
|
3243
|
-
name: "operation.retry",
|
|
3244
|
-
stage: requestCategory === "session_read" ? "session_read" : "processing",
|
|
3245
|
-
requestCategory,
|
|
3246
|
-
attempt
|
|
3247
|
-
});
|
|
3248
|
-
}
|
|
3249
|
-
});
|
|
3250
|
-
try {
|
|
3251
|
-
const result = await api.getUnifiedCheckoutSession(sessionId);
|
|
3252
|
-
if (firstByteDuration !== void 0) {
|
|
3253
|
-
this.telemetryReporter?.log({
|
|
3254
|
-
name: "session.request.first_byte",
|
|
3255
|
-
stage: "session_first_byte",
|
|
3256
|
-
requestCategory: "session_read",
|
|
3257
|
-
statusClass: "2xx"
|
|
3258
|
-
});
|
|
3259
|
-
this.telemetryReporter?.performance({
|
|
3260
|
-
stage: "session_first_byte",
|
|
3261
|
-
durationMs: firstByteDuration,
|
|
3262
|
-
durationMode: "machine",
|
|
3263
|
-
requestCategory: "session_read",
|
|
3264
|
-
statusClass: "2xx"
|
|
3265
|
-
});
|
|
3266
|
-
}
|
|
3267
|
-
this.telemetryReporter?.log({
|
|
3268
|
-
name: "session.request.completed",
|
|
3269
|
-
stage: "session_complete",
|
|
3270
|
-
requestCategory: "session_read",
|
|
3271
|
-
statusClass: "2xx"
|
|
3272
|
-
});
|
|
3273
|
-
this.telemetryReporter?.log({
|
|
3274
|
-
name: "checkout.data.ready",
|
|
3275
|
-
stage: "checkout_data_ready"
|
|
3276
|
-
});
|
|
3277
|
-
this.telemetryReporter?.performance({
|
|
3278
|
-
stage: "session_complete",
|
|
3279
|
-
durationMs: this.now() - started,
|
|
3280
|
-
durationMode: "machine",
|
|
3281
|
-
requestCategory: "session_read",
|
|
3282
|
-
statusClass: "2xx"
|
|
3283
|
-
});
|
|
3284
|
-
return result;
|
|
3285
|
-
} catch (error) {
|
|
3286
|
-
const statusCode = error instanceof import_shared6.FloPayError ? error.statusCode : void 0;
|
|
3287
|
-
this.telemetryReporter?.error({
|
|
3288
|
-
errorCode: error instanceof import_shared6.FloPayError && error.code === "checkout_processing_timeout" ? "REQUEST_TIMEOUT" : "NETWORK_REQUEST_FAILED",
|
|
3289
|
-
stage: "session_read",
|
|
3290
|
-
provider: "flo",
|
|
3291
|
-
paymentMethodCategory: "unknown"
|
|
3292
|
-
});
|
|
3293
|
-
this.telemetryReporter?.performance({
|
|
3294
|
-
stage: "session_complete",
|
|
3295
|
-
durationMs: this.now() - started,
|
|
3296
|
-
durationMode: "machine",
|
|
3297
|
-
requestCategory: "session_read",
|
|
3298
|
-
statusClass: statusCode ? `${Math.floor(statusCode / 100)}xx` : "network_error"
|
|
3299
|
-
});
|
|
3300
|
-
throw error;
|
|
3301
|
-
}
|
|
3302
|
-
}
|
|
3303
|
-
/**
|
|
3304
|
-
* Returns the raw underlying provider instance (e.g. Stripe object).
|
|
3305
|
-
* Used internally by components that need direct provider access,
|
|
3306
|
-
* such as PayPal which requires its own Elements instance.
|
|
3307
|
-
*/
|
|
3308
|
-
getRawProvider() {
|
|
3309
|
-
return this.provider.getRawProvider();
|
|
3310
|
-
}
|
|
3311
|
-
/** Tears down the SDK instance and releases resources. */
|
|
3312
|
-
destroy() {
|
|
3313
|
-
this.telemetryReporter?.log({ name: "checkout.unmount", stage: "unmount" });
|
|
3314
|
-
this.telemetryReporter?.destroy();
|
|
3315
|
-
this.currentElements?.destroy();
|
|
3316
|
-
this.currentElements = null;
|
|
3317
|
-
this.provider.destroy();
|
|
3318
|
-
}
|
|
3319
|
-
};
|
|
3320
|
-
function createInstrumentedFloPay(provider, config, reporter) {
|
|
3321
|
-
const InstrumentedFloPay = FloPay;
|
|
3322
|
-
return new InstrumentedFloPay(provider, config, reporter);
|
|
3323
|
-
}
|
|
3324
|
-
|
|
3325
|
-
// src/load.ts
|
|
3326
|
-
var instanceCache = /* @__PURE__ */ new Map();
|
|
3327
|
-
function stableCacheValue(value) {
|
|
3328
|
-
if (Array.isArray(value)) return value.map(stableCacheValue);
|
|
3329
|
-
if (value && typeof value === "object") {
|
|
3330
|
-
return Object.fromEntries(
|
|
3331
|
-
Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, stableCacheValue(entry)])
|
|
3332
|
-
);
|
|
3333
|
-
}
|
|
3334
|
-
return value;
|
|
3335
|
-
}
|
|
3336
|
-
function instanceCacheKey(publishableKey, options) {
|
|
3337
|
-
return JSON.stringify([
|
|
3338
|
-
publishableKey,
|
|
3339
|
-
(0, import_shared7.resolveBillingApiUrl)(options?.billingApiUrl),
|
|
3340
|
-
options?.telemetry !== false,
|
|
3341
|
-
options?.locale ?? "auto",
|
|
3342
|
-
options?.apiVersion ?? null,
|
|
3343
|
-
stableCacheValue(options?.appearance ?? null)
|
|
3344
|
-
]);
|
|
3345
|
-
}
|
|
3346
|
-
var initializationCache = /* @__PURE__ */ new Map();
|
|
3347
|
-
async function loadFloPay(publishableKey, options) {
|
|
3348
|
-
if (!publishableKey) {
|
|
3349
|
-
const reporter = new TelemetryReporter({
|
|
3350
|
-
billingApiUrl: (0, import_shared7.resolveBillingApiUrl)(options?.billingApiUrl),
|
|
3351
|
-
sdkVersion: import_shared7.SDK_VERSION,
|
|
3352
|
-
enabled: options?.telemetry !== false
|
|
3353
|
-
});
|
|
3354
|
-
reporter.error({
|
|
3355
|
-
errorCode: "CONFIGURATION_INVALID",
|
|
3356
|
-
stage: "sdk_initialize",
|
|
3357
|
-
paymentMethodCategory: "unknown"
|
|
3358
|
-
});
|
|
3359
|
-
void reporter.flush().catch(() => {
|
|
3360
|
-
}).finally(() => reporter.destroy());
|
|
3361
|
-
throw new import_shared7.FloPayError(
|
|
3362
|
-
"A publishable key is required to initialize FloPay.",
|
|
3363
|
-
"validation_error",
|
|
3364
|
-
{ param: "publishableKey" }
|
|
3365
|
-
);
|
|
3366
|
-
}
|
|
3367
|
-
const cacheKey = instanceCacheKey(publishableKey, options);
|
|
3368
|
-
const cached = instanceCache.get(cacheKey);
|
|
3369
|
-
if (cached) {
|
|
3370
|
-
if (options?.telemetry !== false) {
|
|
3371
|
-
getFloPayTelemetryBridge(cached)?.log({
|
|
3372
|
-
name: "sdk.cache.hit",
|
|
3373
|
-
stage: "sdk_initialize"
|
|
3374
|
-
});
|
|
3375
|
-
}
|
|
3376
|
-
return cached;
|
|
3377
|
-
}
|
|
3378
|
-
const initializing = initializationCache.get(cacheKey);
|
|
3379
|
-
if (initializing) return initializing;
|
|
3380
|
-
const config = {
|
|
3381
|
-
...options,
|
|
3382
|
-
publishableKey
|
|
3383
|
-
};
|
|
3384
|
-
const initialization = (async () => {
|
|
3385
|
-
const reporter = new TelemetryReporter({
|
|
3386
|
-
billingApiUrl: (0, import_shared7.resolveBillingApiUrl)(config.billingApiUrl),
|
|
3387
|
-
sdkVersion: import_shared7.SDK_VERSION,
|
|
3388
|
-
enabled: config.telemetry !== false
|
|
3389
|
-
});
|
|
3390
|
-
const initializationStarted = reporter.now();
|
|
3391
|
-
reporter.log({ name: "sdk.initialize.started", stage: "sdk_initialize" });
|
|
3392
|
-
reporter.log({ name: "sdk.cache.miss", stage: "sdk_initialize" });
|
|
3393
|
-
reporter.log({
|
|
3394
|
-
name: "provider.load.started",
|
|
3395
|
-
stage: "provider_load",
|
|
3396
|
-
provider: "stripe"
|
|
3397
|
-
});
|
|
3398
|
-
const adapter = new StripeAdapter();
|
|
3399
|
-
try {
|
|
3400
|
-
await adapter.initialize(config);
|
|
3401
|
-
} catch (error) {
|
|
3402
|
-
reporter.error({
|
|
3403
|
-
errorCode: "SDK_INITIALIZATION_FAILED",
|
|
3404
|
-
stage: "sdk_initialize",
|
|
3405
|
-
provider: "stripe",
|
|
3406
|
-
paymentMethodCategory: "unknown"
|
|
3407
|
-
});
|
|
3408
|
-
reporter.destroy();
|
|
3409
|
-
throw error;
|
|
3410
|
-
}
|
|
3411
|
-
reporter.log({ name: "provider.ready", stage: "provider_ready", provider: "stripe" });
|
|
3412
|
-
reporter.log({
|
|
3413
|
-
name: "provider.availability.checked",
|
|
3414
|
-
stage: "provider_ready",
|
|
3415
|
-
provider: "stripe"
|
|
3416
|
-
});
|
|
3417
|
-
reporter.log({ name: "sdk.initialize.ready", stage: "sdk_initialize" });
|
|
3418
|
-
const initializationDuration = reporter.now() - initializationStarted;
|
|
3419
|
-
reporter.performance({
|
|
3420
|
-
stage: "sdk_initialize",
|
|
3421
|
-
durationMs: initializationDuration,
|
|
3422
|
-
durationMode: "machine",
|
|
3423
|
-
provider: "stripe"
|
|
3424
|
-
});
|
|
3425
|
-
reporter.performance({
|
|
3426
|
-
stage: "provider_ready",
|
|
3427
|
-
durationMs: initializationDuration,
|
|
3428
|
-
durationMode: "machine",
|
|
3429
|
-
provider: "stripe"
|
|
3430
|
-
});
|
|
3431
|
-
const instance = createInstrumentedFloPay(adapter, config, reporter);
|
|
3432
|
-
instanceCache.set(cacheKey, instance);
|
|
3433
|
-
return instance;
|
|
3434
|
-
})();
|
|
3435
|
-
initializationCache.set(cacheKey, initialization);
|
|
3436
|
-
try {
|
|
3437
|
-
return await initialization;
|
|
3438
|
-
} finally {
|
|
3439
|
-
if (initializationCache.get(cacheKey) === initialization) {
|
|
3440
|
-
initializationCache.delete(cacheKey);
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3444
|
-
|
|
3445
|
-
// src/create-checkout-session.ts
|
|
3446
|
-
var import_shared8 = require("@flopay/shared");
|
|
3447
|
-
var MAX_COUPON_CODES = 5;
|
|
3448
|
-
function buildCheckoutSessionError(status, payload) {
|
|
3449
|
-
const nested = payload?.error;
|
|
3450
|
-
const code = readErrorString(payload?.code) ?? readErrorString(nested?.code) ?? `http_${status}`;
|
|
3451
|
-
const message = readErrorString(payload?.message) ?? readErrorString(nested?.message) ?? defaultMessageForCode(code, status);
|
|
3452
|
-
return new import_shared8.FloPayError(message, "api_error", { code, statusCode: status });
|
|
3453
|
-
}
|
|
3454
|
-
function defaultMessageForCode(code, status) {
|
|
3455
|
-
switch (code) {
|
|
3456
|
-
case "CouponLimitExceeded":
|
|
3457
|
-
return `Too many coupon codes \u2014 a checkout session accepts at most ${MAX_COUPON_CODES}.`;
|
|
3458
|
-
case "CouponCurrencyUnsupported":
|
|
3459
|
-
return "One of the applied coupons has no price configured for the cart currency.";
|
|
3460
|
-
default:
|
|
3461
|
-
return `Failed to create checkout session (HTTP ${status}).`;
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
async function createCheckoutSessionCore(options, onFirstByte) {
|
|
3465
|
-
const {
|
|
3466
|
-
billingApiUrl,
|
|
3467
|
-
checkoutBaseUrl,
|
|
3468
|
-
items = [],
|
|
3469
|
-
subscriptions = [],
|
|
3470
|
-
products,
|
|
3471
|
-
account,
|
|
3472
|
-
successUrl,
|
|
3473
|
-
cancelUrl,
|
|
3474
|
-
checkoutMode = "confirm",
|
|
3475
|
-
couponCodes = [],
|
|
3476
|
-
tagsData,
|
|
3477
|
-
redirectParams = {},
|
|
3478
|
-
setCookie = true,
|
|
3479
|
-
timeoutMs = 12e3,
|
|
3480
|
-
clientId,
|
|
3481
|
-
currency,
|
|
3482
|
-
utmMetadata,
|
|
3483
|
-
idempotencyKey
|
|
3484
|
-
} = options;
|
|
3485
|
-
const resolvedIdempotencyKey = (0, import_shared8.resolveIdempotencyKey)(idempotencyKey);
|
|
3486
|
-
if (couponCodes.length > MAX_COUPON_CODES) {
|
|
3487
|
-
throw new import_shared8.FloPayError(
|
|
3488
|
-
`Too many coupon codes \u2014 a checkout session accepts at most ${MAX_COUPON_CODES}.`,
|
|
3489
|
-
"validation_error",
|
|
3490
|
-
{ code: "CouponLimitExceeded", param: "couponCodes" }
|
|
3491
|
-
);
|
|
3492
|
-
}
|
|
3493
|
-
const wireProducts = products ?? (0, import_shared8.foldIntoProducts)(items, subscriptions);
|
|
3494
|
-
const sessionCurrency = (0, import_shared8.resolveSessionCurrency)(currency, items, subscriptions, wireProducts);
|
|
3495
|
-
if (!sessionCurrency) {
|
|
3496
|
-
throw new import_shared8.FloPayError(
|
|
3497
|
-
"currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.",
|
|
3498
|
-
"validation_error",
|
|
3499
|
-
{ code: "CurrencyRequired", param: "currency" }
|
|
3500
|
-
);
|
|
3501
|
-
}
|
|
3502
|
-
const payload = {
|
|
3503
|
-
clientId,
|
|
3504
|
-
checkoutVersion: import_shared8.SDK_VERSION,
|
|
3505
|
-
successUrl,
|
|
3506
|
-
cancelUrl,
|
|
3507
|
-
currency: sessionCurrency,
|
|
3508
|
-
checkoutMode,
|
|
3509
|
-
products: wireProducts.map((product) => (0, import_shared8.buildProductPayload)(product, sessionCurrency)),
|
|
3510
|
-
accountData: {
|
|
3511
|
-
userId: account.userId,
|
|
3512
|
-
firstName: account.firstName ?? null,
|
|
3513
|
-
lastName: account.lastName ?? null,
|
|
3514
|
-
email: account.email,
|
|
3515
|
-
country: account.country ?? null,
|
|
3516
|
-
gender: account.gender ?? null,
|
|
3517
|
-
city: account.city ?? null,
|
|
3518
|
-
state: account.state ?? null,
|
|
3519
|
-
zip: account.zip ?? null,
|
|
3520
|
-
addressLine1: account.addressLine1 ?? null,
|
|
3521
|
-
addressLine2: account.addressLine2 ?? null
|
|
3522
|
-
},
|
|
3523
|
-
couponCodes
|
|
3524
|
-
};
|
|
3525
|
-
if (tagsData) {
|
|
3526
|
-
payload["tagsData"] = tagsData;
|
|
3527
|
-
}
|
|
3528
|
-
if (utmMetadata?.length) {
|
|
3529
|
-
payload["utmMetadata"] = utmMetadata;
|
|
3530
|
-
}
|
|
3531
|
-
const url = `${billingApiUrl.replace(/\/+$/, "")}/v1/checkouts/sessions`;
|
|
3532
|
-
const controller = new AbortController();
|
|
3533
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3534
|
-
const headers = { "Content-Type": "application/json" };
|
|
3535
|
-
if (resolvedIdempotencyKey) {
|
|
3536
|
-
headers[import_shared8.IDEMPOTENCY_KEY_HEADER] = resolvedIdempotencyKey;
|
|
3537
|
-
}
|
|
3538
|
-
let status;
|
|
3539
|
-
let body;
|
|
3540
|
-
try {
|
|
3541
|
-
const response = await fetch(url, {
|
|
3542
|
-
method: "POST",
|
|
3543
|
-
headers,
|
|
3544
|
-
body: JSON.stringify(payload),
|
|
3545
|
-
signal: controller.signal
|
|
3546
|
-
});
|
|
3547
|
-
try {
|
|
3548
|
-
onFirstByte?.(response.status);
|
|
3549
|
-
} catch {
|
|
3550
|
-
}
|
|
3551
|
-
status = response.status;
|
|
3552
|
-
try {
|
|
3553
|
-
body = await response.json();
|
|
3554
|
-
} catch {
|
|
3555
|
-
}
|
|
3556
|
-
} finally {
|
|
3557
|
-
clearTimeout(timer);
|
|
3558
|
-
}
|
|
3559
|
-
if (status >= 400) {
|
|
3560
|
-
throw buildCheckoutSessionError(status, body);
|
|
3561
|
-
}
|
|
3562
|
-
if (status === 201) {
|
|
3563
|
-
const uuid = body?.data?.uuid;
|
|
3564
|
-
const nonce = body?.data?.nonce;
|
|
3565
|
-
if (!uuid) {
|
|
3566
|
-
throw new Error("Checkout session created but no UUID was returned by the billing API");
|
|
3567
|
-
}
|
|
3568
|
-
if (!nonce) {
|
|
3569
|
-
throw new import_shared8.FloPayError(
|
|
3570
|
-
"Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.",
|
|
3571
|
-
"api_error",
|
|
3572
|
-
{ code: "MissingCheckoutSessionToken" }
|
|
3573
|
-
);
|
|
3574
|
-
}
|
|
3575
|
-
if (wireProducts.length || sessionCurrency) {
|
|
3576
|
-
cacheSessionDisplayData(uuid, {
|
|
3577
|
-
currency: sessionCurrency,
|
|
3578
|
-
products: wireProducts.map((p) => ({
|
|
3579
|
-
code: p.code ?? p.providerItemId ?? p.providerPlanId,
|
|
3580
|
-
type: p.type,
|
|
3581
|
-
name: p.name ?? p.itemName ?? p.providerItemName ?? p.subscriptionName ?? p.providerPlanName ?? null,
|
|
3582
|
-
totalAmount: p.totalAmount,
|
|
3583
|
-
overrideAmount: p.overrideAmount,
|
|
3584
|
-
currency: p.currency ?? sessionCurrency
|
|
3585
|
-
}))
|
|
3586
|
-
});
|
|
3587
|
-
}
|
|
3588
|
-
const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
|
|
3589
|
-
redirectUrl.searchParams.set("id", uuid);
|
|
3590
|
-
for (const [key, value] of Object.entries(redirectParams)) {
|
|
3591
|
-
redirectUrl.searchParams.set(key, value);
|
|
3592
|
-
}
|
|
3593
|
-
if (setCookie && typeof window !== "undefined" && typeof document !== "undefined") {
|
|
3594
|
-
const checkoutData = JSON.stringify({ origin_url: cancelUrl });
|
|
3595
|
-
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
|
3596
|
-
document.cookie = `checkout_data=${encodeURIComponent(checkoutData)}; domain=.${domain}; path=/; max-age=3600; SameSite=Lax; Secure;`;
|
|
3597
|
-
document.cookie = `flopay_checkout_token=${encodeURIComponent(nonce)}; domain=.${domain}; path=/; max-age=3600; SameSite=Lax; Secure;`;
|
|
3598
|
-
}
|
|
3599
|
-
if (typeof window !== "undefined") {
|
|
3600
|
-
window.location.href = redirectUrl.toString();
|
|
3601
|
-
}
|
|
3602
|
-
return { status: 201, redirectUrl: redirectUrl.toString(), nonce };
|
|
3603
|
-
}
|
|
3604
|
-
if (status === 204) {
|
|
3605
|
-
if (typeof window !== "undefined") {
|
|
3606
|
-
window.location.href = successUrl;
|
|
3607
|
-
}
|
|
3608
|
-
return { status: 204 };
|
|
3609
|
-
}
|
|
3610
|
-
return { status };
|
|
3611
|
-
}
|
|
3612
|
-
async function createCheckoutSession(options) {
|
|
3613
|
-
const telemetry = beginCreateSessionTelemetry(options);
|
|
3614
|
-
const recordFirstByte = createFirstByteRecorder(telemetry);
|
|
3615
|
-
try {
|
|
3616
|
-
const result = await createCheckoutSessionCore(options, recordFirstByte);
|
|
3617
|
-
completeCreateSessionTelemetry(telemetry, result);
|
|
3618
|
-
return result;
|
|
3619
|
-
} catch (error) {
|
|
3620
|
-
failCreateSessionTelemetry(telemetry.reporter, error);
|
|
3621
|
-
throw error;
|
|
3622
|
-
} finally {
|
|
3623
|
-
finishCreateSessionTelemetry(telemetry.reporter);
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
function createFirstByteRecorder(telemetry) {
|
|
3627
|
-
let recorded = false;
|
|
3628
|
-
return (status) => {
|
|
3629
|
-
if (recorded) return;
|
|
3630
|
-
recorded = true;
|
|
3631
|
-
const statusClass = `${Math.floor(status / 100)}xx`;
|
|
3632
|
-
telemetry.reporter.log({
|
|
3633
|
-
name: "session.request.first_byte",
|
|
3634
|
-
stage: "session_first_byte",
|
|
3635
|
-
requestCategory: "session_create",
|
|
3636
|
-
statusClass
|
|
3637
|
-
});
|
|
3638
|
-
telemetry.reporter.performance({
|
|
3639
|
-
stage: "session_first_byte",
|
|
3640
|
-
durationMs: telemetry.reporter.now() - telemetry.startedAt,
|
|
3641
|
-
durationMode: "machine",
|
|
3642
|
-
requestCategory: "session_create",
|
|
3643
|
-
statusClass
|
|
3644
|
-
});
|
|
3645
|
-
};
|
|
3646
|
-
}
|
|
3647
|
-
function beginCreateSessionTelemetry(options) {
|
|
3648
|
-
const reporter = new TelemetryReporter({
|
|
3649
|
-
billingApiUrl: options.billingApiUrl,
|
|
3650
|
-
sdkVersion: import_shared8.SDK_VERSION,
|
|
3651
|
-
enabled: options.telemetry !== false
|
|
3652
|
-
});
|
|
3653
|
-
const startedAt = reporter.now();
|
|
3654
|
-
reporter.log({
|
|
3655
|
-
name: "session.create.started",
|
|
3656
|
-
stage: "session_create",
|
|
3657
|
-
requestCategory: "session_create"
|
|
3658
|
-
});
|
|
3659
|
-
return { reporter, startedAt };
|
|
3660
|
-
}
|
|
3661
|
-
function completeCreateSessionTelemetry(telemetry, result) {
|
|
3662
|
-
const statusClass = `${Math.floor(result.status / 100)}xx`;
|
|
3663
|
-
telemetry.reporter.log({
|
|
3664
|
-
name: "session.request.completed",
|
|
3665
|
-
stage: "session_complete",
|
|
3666
|
-
requestCategory: "session_create",
|
|
3667
|
-
statusClass
|
|
3668
|
-
});
|
|
3669
|
-
telemetry.reporter.performance({
|
|
3670
|
-
stage: "session_complete",
|
|
3671
|
-
durationMs: telemetry.reporter.now() - telemetry.startedAt,
|
|
3672
|
-
durationMode: "machine",
|
|
3673
|
-
requestCategory: "session_create",
|
|
3674
|
-
statusClass
|
|
3675
|
-
});
|
|
3676
|
-
}
|
|
3677
|
-
function failCreateSessionTelemetry(reporter, error) {
|
|
3678
|
-
if (error instanceof import_shared8.FloPayError && error.type === "validation_error") {
|
|
3679
|
-
reporter.terminal({
|
|
3680
|
-
outcome: "validation_rejected",
|
|
3681
|
-
stage: "session_create",
|
|
3682
|
-
requestCategory: "session_create"
|
|
3683
|
-
});
|
|
3684
|
-
return;
|
|
3685
|
-
}
|
|
3686
|
-
const statusCode = error instanceof import_shared8.FloPayError ? error.statusCode : void 0;
|
|
3687
|
-
reporter.error({
|
|
3688
|
-
errorCode: error instanceof Error && error.name === "AbortError" ? "REQUEST_TIMEOUT" : error instanceof TypeError ? "NETWORK_REQUEST_FAILED" : "CHECKOUT_SESSION_CREATE_FAILED",
|
|
3689
|
-
stage: "session_create",
|
|
3690
|
-
requestCategory: "session_create",
|
|
3691
|
-
statusClass: error instanceof Error && error.name === "AbortError" ? "timeout" : statusCode ? `${Math.floor(statusCode / 100)}xx` : "network_error"
|
|
3692
|
-
});
|
|
3693
|
-
}
|
|
3694
|
-
function finishCreateSessionTelemetry(reporter) {
|
|
3695
|
-
void reporter.flush().catch(() => {
|
|
3696
|
-
}).finally(() => reporter.destroy());
|
|
3697
|
-
}
|
|
3698
|
-
async function createCheckoutSessionWithRetries(options) {
|
|
3699
|
-
const { maxRetries = 3, ...sessionOptions } = options;
|
|
3700
|
-
const telemetry = beginCreateSessionTelemetry(options);
|
|
3701
|
-
const recordFirstByte = createFirstByteRecorder(telemetry);
|
|
3702
|
-
if (!Number.isFinite(maxRetries) || !Number.isInteger(maxRetries) || maxRetries <= 0) {
|
|
3703
|
-
telemetry.reporter.terminal({
|
|
3704
|
-
outcome: "validation_rejected",
|
|
3705
|
-
stage: "session_create",
|
|
3706
|
-
requestCategory: "session_create"
|
|
3707
|
-
});
|
|
3708
|
-
finishCreateSessionTelemetry(telemetry.reporter);
|
|
3709
|
-
throw new Error("Number of retries must be greater than 0");
|
|
3710
|
-
}
|
|
3711
|
-
const attemptOptions = {
|
|
3712
|
-
...sessionOptions,
|
|
3713
|
-
idempotencyKey: (0, import_shared8.resolveIdempotencyKey)(sessionOptions.idempotencyKey)
|
|
3714
|
-
};
|
|
3715
|
-
let lastErr;
|
|
3716
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3717
|
-
try {
|
|
3718
|
-
const result = await createCheckoutSessionCore(attemptOptions, recordFirstByte);
|
|
3719
|
-
completeCreateSessionTelemetry(telemetry, result);
|
|
3720
|
-
finishCreateSessionTelemetry(telemetry.reporter);
|
|
3721
|
-
return result;
|
|
3722
|
-
} catch (err) {
|
|
3723
|
-
lastErr = err;
|
|
3724
|
-
const isTransportAbort = err instanceof Error && err.name === "AbortError";
|
|
3725
|
-
const isInProgressReplay = err instanceof import_shared8.FloPayError && err.code === import_shared8.IDEMPOTENCY_IN_PROGRESS_CODE;
|
|
3726
|
-
if ((isTransportAbort || isInProgressReplay) && attempt < maxRetries) {
|
|
3727
|
-
telemetry.reporter.log({
|
|
3728
|
-
name: "operation.retry",
|
|
3729
|
-
stage: "session_create",
|
|
3730
|
-
requestCategory: "session_create",
|
|
3731
|
-
attempt: attempt + 1
|
|
3732
|
-
});
|
|
3733
|
-
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempt)));
|
|
3734
|
-
continue;
|
|
3735
|
-
}
|
|
3736
|
-
failCreateSessionTelemetry(telemetry.reporter, err);
|
|
3737
|
-
finishCreateSessionTelemetry(telemetry.reporter);
|
|
3738
|
-
throw err;
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3741
|
-
failCreateSessionTelemetry(telemetry.reporter, lastErr);
|
|
3742
|
-
finishCreateSessionTelemetry(telemetry.reporter);
|
|
3743
|
-
throw lastErr ?? new Error("Unknown error during checkout session creation");
|
|
3744
|
-
}
|
|
3745
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
3746
|
-
0 && (module.exports = {
|
|
3747
|
-
FloPay,
|
|
3748
|
-
FloPayElements,
|
|
3749
|
-
PaymentAPI,
|
|
3750
|
-
PciVaultCardCapture,
|
|
3751
|
-
StripeAdapter,
|
|
3752
|
-
cacheSessionDisplayData,
|
|
3753
|
-
clearSessionDisplayData,
|
|
3754
|
-
createCheckoutSession,
|
|
3755
|
-
createCheckoutSessionWithRetries,
|
|
3756
|
-
getSessionDisplayData,
|
|
3757
|
-
loadFloPay
|
|
3758
|
-
});
|
|
3759
|
-
//# sourceMappingURL=index.cjs.map
|
|
1
|
+
"use strict";var le=Object.defineProperty;var Qe=Object.getOwnPropertyDescriptor;var Je=Object.getOwnPropertyNames;var Xe=Object.prototype.hasOwnProperty;var Ze=(n,e)=>{for(var t in e)le(n,t,{get:e[t],enumerable:!0})},et=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Je(e))!Xe.call(n,s)&&s!==t&&le(n,s,{get:()=>e[s],enumerable:!(r=Qe(e,s))||r.enumerable});return n};var tt=n=>et(le({},"__esModule",{value:!0}),n);var Rt={};Ze(Rt,{FloPay:()=>G,FloPayElements:()=>B,PaymentAPI:()=>A,PciVaultCardCapture:()=>F,StripeAdapter:()=>K,cacheSessionDisplayData:()=>x,clearSessionDisplayData:()=>te,createCheckoutSession:()=>je,createCheckoutSessionWithRetries:()=>Ye,getSessionDisplayData:()=>ee,loadFloPay:()=>Be});module.exports=tt(Rt);var R=require("@flopay/shared");var xe=require("@stripe/stripe-js"),h=require("@flopay/shared");var d=require("@flopay/shared");function I(n){return typeof n=="string"&&n.trim()?n:void 0}function _e(n){if(typeof n=="string")return n.trim()?n:void 0;if(Array.isArray(n))return n.filter(t=>typeof t=="string"&&t.trim().length>0).join("; ")||void 0}var rt="flopay_session_display:";var X=new Map;function Z(n){return`${rt}${n}`}function ce(){if(typeof window>"u")return null;try{return window.sessionStorage}catch{return null}}function x(n,e,t){if(!n)return;let r=t?.ttlMs??36e5,s={data:e,expiresAt:Date.now()+r},o=ce();if(o)try{o.setItem(Z(n),JSON.stringify(s));return}catch{}X.set(n,s)}function ee(n){if(!n)return null;let e=ce();if(e)try{let r=e.getItem(Z(n));if(r){let s=JSON.parse(r);if(s&&typeof s.expiresAt=="number"&&s.expiresAt>Date.now())return s.data;e.removeItem(Z(n))}}catch{}let t=X.get(n);if(t){if(t.expiresAt>Date.now())return t.data;X.delete(n)}return null}function te(n){if(!n)return;X.delete(n);let e=ce();if(e)try{e.removeItem(Z(n))}catch{}}var T=require("@flopay/shared"),nt="/v1/sdk-telemetry/events",de=16,ot=64,st=1500,we=1e3,it=64,at="00000000-0000-4000-8000-000000000000",lt={technical_error:8,lifecycle:32,expected_outcome:32,performance:24};function L(){try{return globalThis.crypto.randomUUID()}catch{let n=new Uint8Array(16);try{globalThis.crypto.getRandomValues(n)}catch{for(let t=0;t<n.length;t+=1)n[t]=Math.floor(Math.random()*256)}n[6]=n[6]&15|64,n[8]=n[8]&63|128;let e=[...n].map(t=>t.toString(16).padStart(2,"0")).join("");return`${e.slice(0,8)}-${e.slice(8,12)}-${e.slice(12,16)}-${e.slice(16,20)}-${e.slice(20)}`}}function ct(n){try{return new TextEncoder().encode(n).byteLength}catch{return n.length}}function dt(n){return JSON.stringify([n.code,n.stage,n.provider,n.attempt,n.statusClass,n.requestCategory,n.paymentMethodCategory,n.checkoutMode,n.layout])}function _(){return globalThis.performance?.now()??0}var v=class{constructor(e){this.ingestionDisabled=!1;this.queue=[];this.sequence=0;this.flushTimer=null;this.flushInFlight=null;this.reportedFailures=new Map;this.checkoutContext={};this.checkoutStartedAt=null;this.destroyed=!1;this.pageExitHandler=()=>{this.drainQueue()};this.visibilityHandler=()=>{document.visibilityState==="hidden"&&this.flush()};this.eventCounts={technical_error:0,lifecycle:0,expected_outcome:0,performance:0};this.endpoint=`${e.billingApiUrl.replace(/\/+$/,"")}${nt}`,this.sdkPackage=e.sdkPackage??"@flopay/js",this.sdkVersion=e.sdkVersion,this.correlationId=L(),this.merchantEnabled=e.enabled!==!1,this.clock=e.clock??_,this.browserTransportAvailable=typeof window<"u"&&typeof document<"u",this.browserTransportAvailable&&(window.addEventListener("pagehide",this.pageExitHandler),document.addEventListener("visibilitychange",this.visibilityHandler))}log(e){this.canCollect()&&this.enqueue((0,T.buildTelemetryLogEvent)({...this.checkoutContext,...e,eventId:L(),sequence:this.sequence++}))}error(e){if(!this.canCollect())return;let t=(0,T.buildTelemetryErrorEvent)({...this.checkoutContext,...e,eventId:at,sequence:0}),r=dt(t),s=this.now();this.pruneReportedFailures(s);let o=this.reportedFailures.get(r);if(o!==void 0&&s>=o&&s-o<we){this.log({name:"operation.deduplicated",stage:e.stage,provider:e.provider,paymentMethodCategory:e.paymentMethodCategory,attempt:e.attempt});return}this.rememberReportedFailure(r,s),this.enqueue((0,T.buildTelemetryErrorEvent)({...this.checkoutContext,...e,eventId:L(),sequence:this.sequence++}))}performance(e){this.canCollect()&&this.enqueue((0,T.buildTelemetryPerformanceEvent)({...this.checkoutContext,...e,eventId:L(),sequence:this.sequence++}))}terminal(e){if(this.canCollect()&&(this.enqueue((0,T.buildTelemetryTerminalEvent)({...this.checkoutContext,...e,eventId:L(),sequence:this.sequence++})),e.outcome!=="action_required"&&this.checkoutStartedAt!==null)){let t=this.checkoutStartedAt;this.checkoutStartedAt=null,this.performance({stage:"total_journey",durationMs:Math.max(0,this.now()-t),durationMode:"total",provider:e.provider,paymentMethodCategory:e.paymentMethodCategory})}}now(){try{return this.clock()}catch{return _()}}pruneReportedFailures(e){for(let[t,r]of this.reportedFailures)(e<r||e-r>=we)&&this.reportedFailures.delete(t)}rememberReportedFailure(e,t){for(;this.reportedFailures.size>=it;){let r=this.reportedFailures.keys().next();if(r.done)break;this.reportedFailures.delete(r.value)}this.reportedFailures.set(e,t)}setCheckoutContext(e){this.checkoutContext={checkoutMode:e.checkoutMode,layout:e.layout}}beginCheckout(e={}){return this.canCollect()?(this.drainQueue(),this.setCheckoutContext(e),this.sequence=0,this.reportedFailures.clear(),this.eventCounts={technical_error:0,lifecycle:0,expected_outcome:0,performance:0},this.checkoutStartedAt=this.now(),this.checkoutStartedAt):0}enqueue(e){if(this.canCollect()&&!(this.queue.length>=ot||this.eventCounts[e.class]>=lt[e.class])){if(this.eventCounts[e.class]+=1,this.queue.push(e),this.queue.length>=de){this.flush();return}this.scheduleFlush()}}canCollect(){return this.browserTransportAvailable&&this.merchantEnabled&&!this.ingestionDisabled&&!this.destroyed}async flush(){if(this.flushInFlight)return this.flushInFlight;if(!this.browserTransportAvailable||this.destroyed||this.ingestionDisabled||this.queue.length===0)return;this.clearFlushTimer();let e=this.queue.splice(0,de);return this.flushInFlight=this.sendBatch(e).finally(()=>{this.flushInFlight=null,this.queue.length>0&&this.scheduleFlush()}),this.flushInFlight}destroy(){this.destroyed||(this.drainQueue(),this.destroyed=!0,this.clearFlushTimer(),this.browserTransportAvailable&&(window.removeEventListener("pagehide",this.pageExitHandler),document.removeEventListener("visibilitychange",this.visibilityHandler)),this.queue.splice(0),this.reportedFailures.clear())}disable(){this.merchantEnabled=!1,this.queue.splice(0),this.reportedFailures.clear(),this.clearFlushTimer()}drainQueue(){if(!(!this.browserTransportAvailable||this.destroyed||this.ingestionDisabled||this.queue.length===0))for(this.clearFlushTimer();this.queue.length>0;){let e=this.queue.splice(0,de);this.sendBatch(e)}}async sendBatch(e){if(!this.browserTransportAvailable||this.ingestionDisabled)return;let t=(0,T.serializeTelemetryBatch)(e,{correlationId:this.correlationId,sdkPackage:this.sdkPackage,sdkVersion:this.sdkVersion,batchId:L()});if(ct(t)>T.TELEMETRY_MAX_BATCH_BYTES)return;let r=typeof AbortController>"u"?null:new AbortController,s=null;try{let o=fetch(this.endpoint,{method:"POST",headers:{"content-type":"text/plain;charset=UTF-8"},body:t,credentials:"omit",keepalive:!0,referrerPolicy:"no-referrer",signal:r?.signal}).then(async l=>l.status!==202?null:(await l.json().catch(()=>null))?.status==="disabled"?"disabled":null).catch(()=>null),i=new Promise(l=>{s=setTimeout(()=>{r?.abort(),l(null)},st)});await Promise.race([o,i])==="disabled"&&this.disableFromIngestion()}catch{}finally{s&&clearTimeout(s)}}disableFromIngestion(){this.ingestionDisabled=!0,this.queue.splice(0),this.reportedFailures.clear(),this.clearFlushTimer()}scheduleFlush(){this.flushTimer||this.destroyed||this.ingestionDisabled||(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush()},0))}clearFlushTimer(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null)}},Pe=Symbol.for("@flopay/js.telemetry.reporter-factory.v1"),be=globalThis;be[Pe]===void 0&&Object.defineProperty(be,Pe,{configurable:!0,enumerable:!1,writable:!1,value:n=>new v(n)});var Ee=1e3,ut=500,pt=15e3,mt=3e3,yt=1e4;function ht(n){return typeof n=="object"&&n!==null}function D(n){if(n===void 0)return"network_error";let e=`${Math.floor(n/100)}xx`;return e==="2xx"||e==="3xx"||e==="4xx"||e==="5xx"?e:"unknown"}function ke(n,e){return n instanceof Error&&(n.name==="AbortError"||n instanceof d.FloPayError&&n.code==="checkout_processing_timeout")?{errorCode:"REQUEST_TIMEOUT",statusClass:"timeout"}:n instanceof TypeError?{errorCode:"NETWORK_REQUEST_FAILED",statusClass:"network_error"}:{errorCode:e,statusClass:D(n instanceof d.FloPayError?n.statusCode:void 0)}}function V(n,e){return I(n?.[e])}function Me(n,e){return _e(n?.[e])}function gt(n,e){let t=n?.[e];return typeof t=="number"&&Number.isFinite(t)?t:void 0}function ue(n){return new Promise(e=>setTimeout(e,n))}function re(){return new d.FloPayError("Checkout is still processing. Please try again shortly.","api_error",{code:"checkout_processing_timeout"})}async function N(n,e){let t=await n.json().catch(()=>null),r=ht(t?.error)?t.error:null,s=Me(t,"message")??Me(r,"message")??e,o=V(t,"code")??V(t,"gatewayErrorCode")??V(r,"code")??`http_${n.status}`;return new d.FloPayError(s,"api_error",{code:o,statusCode:n.status})}var H=2,ft=2;async function ne(n,e,t=H,r){let s;for(let o=0;;o++)try{return await fetch(n,e)}catch(i){if(i instanceof Error&&i.name==="AbortError")throw i;if(s=i,o>=t)throw s;try{r?.(o+1)}catch{}await ue(150*2**o)}}function Ct(n){return"now"in n||"onFirstByte"in n||"onSessionCreateFailure"in n||"onRetry"in n}var A=class{constructor(e,t={}){this.baseUrl=e.replace(/\/+$/,"");let r=Ct(t);this.telemetryHooks=r?t:void 0,this.directTelemetry=r||t.telemetry===!1?void 0:new v({billingApiUrl:this.baseUrl,sdkVersion:d.SDK_VERSION})}destroy(){this.directTelemetry?.destroy()}reportDirectFailure(e,t,r,s,o="unknown"){let i=ke(e,t);this.directTelemetry?.error({...i,stage:r,requestCategory:s,paymentMethodCategory:o})}reportAccountSnapshotFailure(e,t){let r=ke(e,"NETWORK_REQUEST_FAILED");if(t==="best_effort"&&r.errorCode==="REQUEST_TIMEOUT"){this.directTelemetry?.log({name:"operation.fallback",stage:"processing",requestCategory:"account_snapshot",statusClass:"timeout"});return}this.directTelemetry?.error({...r,stage:"processing",requestCategory:"account_snapshot",paymentMethodCategory:"unknown"})}telemetryTimestamp(){try{return this.telemetryHooks?.now?.()??this.directTelemetry?.now()??_()}catch{return _()}}beginDirectTelemetryCheckout(e){!this.directTelemetry||this.directTelemetryCheckoutId===e||(this.directTelemetryCheckoutId=e,this.directTelemetry.beginCheckout())}beginDirectTelemetryOperation(){this.directTelemetry&&(this.directTelemetryCheckoutId=void 0,this.directTelemetry.beginCheckout())}adoptDirectTelemetryCheckout(e){e&&(this.directTelemetryCheckoutId=e)}async getCheckoutSession(e,t){this.beginDirectTelemetryCheckout(e);let r=this.telemetryTimestamp();this.directTelemetry?.log({name:"session.read.started",stage:"session_read",requestCategory:"session_read"});let s={[d.FLO_SDK_VERSION_HEADER]:d.SDK_VERSION};t&&(s["x-checkout-session-token"]=t);try{let o=await ne(`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(e)}`,{headers:s},H,c=>{this.telemetryHooks?.onRetry?.("session_read",c),this.directTelemetry?.log({name:"operation.retry",stage:"session_read",requestCategory:"session_read",attempt:c})}),i=Math.max(0,this.telemetryTimestamp()-r);try{this.telemetryHooks?.onFirstByte?.(i)}catch{}let a=`${Math.floor(o.status/100)}xx`;if(this.directTelemetry?.log({name:"session.request.first_byte",stage:"session_first_byte",requestCategory:"session_read",statusClass:a}),this.directTelemetry?.performance({stage:"session_first_byte",durationMs:i,durationMode:"machine",requestCategory:"session_read",statusClass:a}),!o.ok)throw await N(o,"Failed to get checkout session");let l=await o.json();return this.directTelemetry?.log({name:"session.request.completed",stage:"session_complete",requestCategory:"session_read",statusClass:a}),this.directTelemetry?.performance({stage:"session_complete",durationMs:Math.max(0,this.telemetryTimestamp()-r),durationMode:"machine",requestCategory:"session_read",statusClass:a}),{...l,data:this.mergeCachedDisplayData(l.data)}}catch(o){let i=o instanceof d.FloPayError?o.statusCode:void 0;throw this.directTelemetry?.error({errorCode:o instanceof d.FloPayError&&o.code==="checkout_processing_timeout"?"REQUEST_TIMEOUT":"NETWORK_REQUEST_FAILED",stage:"session_read",requestCategory:"session_read",statusClass:i?`${Math.floor(i/100)}xx`:"network_error"}),o}}cacheSessionDisplayData(e,t,r){x(e,t,r)}clearSessionDisplayData(e){te(e)}async getVaultCapture(e,t){this.beginDirectTelemetryCheckout(e);let r=this.telemetryTimestamp();this.directTelemetry?.log({name:"vault.capture.requested",stage:"vault_request",requestCategory:"vault_capture",paymentMethodCategory:"card"});let s={"Content-Type":"application/json"};t&&(s["x-checkout-session-token"]=t);try{let o=await ne(`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(e)}/vault/capture`,{method:"POST",headers:s},H,a=>{this.telemetryHooks?.onRetry?.("vault_capture",a),this.directTelemetry?.log({name:"operation.retry",stage:"vault_request",requestCategory:"vault_capture",paymentMethodCategory:"card",attempt:a})});if(!o.ok)throw await N(o,"Failed to load the secure card form");let i=await o.json();return this.directTelemetry?.performance({stage:"vault_request",durationMs:Math.max(0,this.telemetryTimestamp()-r),durationMode:"machine",requestCategory:"vault_capture",paymentMethodCategory:"card",statusClass:"2xx"}),this.toVaultBlock(i)}catch(o){throw this.reportDirectFailure(o,"VAULT_LOAD_FAILED","vault_request","vault_capture","card"),o}}async getUnifiedCheckoutSession(e,t){let r=await this.getCheckoutSession(e,t),s=this.normalizeRawSession(r.data),o=r.vault;return o&&s.data.session&&(s.data.session.vault=this.toVaultBlock(o)),s}async processPayment(e,t,r){if(this.beginDirectTelemetryCheckout(t.sessionId),!t.nonce)throw this.directTelemetry?.terminal({outcome:"validation_rejected",stage:"processing",requestCategory:"process_payment"}),new d.FloPayError("processPayment requires `nonce` \u2014 pass the value returned from session creation.","validation_error",{code:"MissingCheckoutSessionToken",param:"nonce"});let s=this.telemetryTimestamp();this.directTelemetry?.log({name:"payment.processing.started",stage:"processing",requestCategory:"process_payment"});let{nonce:o,...i}=t,a;try{a=await fetch(`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(t.sessionId)}/process`,{method:"POST",headers:{"Content-Type":"application/json","x-checkout-session-token":o},body:JSON.stringify(i)})}catch(l){throw this.reportDirectFailure(l,"PAYMENT_PROCESSING_FAILED","processing","process_payment"),l}if(!a.ok&&a.status!==202)return this.directTelemetry?.error({errorCode:"PAYMENT_PROCESSING_FAILED",stage:"processing",requestCategory:"process_payment",statusClass:D(a.status)}),a;try{let l=await this.resolveProcessResponse(a,t.sessionId,{...r,nonce:o});return this.directTelemetry?.log({name:"payment.processing.completed",stage:"processing",requestCategory:"process_payment",statusClass:D(l.status)}),this.directTelemetry?.performance({stage:"processing",durationMs:Math.max(0,this.telemetryTimestamp()-s),durationMode:"machine",requestCategory:"process_payment",statusClass:D(l.status)}),l}catch(l){throw l instanceof d.FloPayError&&l.code==="checkout_processing_timeout"||this.reportDirectFailure(l,"PAYMENT_PROCESSING_FAILED","processing","process_payment"),l}}async patchAccountSnapshot(e,t,r,s){this.beginDirectTelemetryCheckout(e);let o=this.telemetryTimestamp();this.directTelemetry?.log({name:"operation.state_transition",stage:"processing",requestCategory:"account_snapshot"});let i=s?.timeoutMs??yt,a=new AbortController,l=()=>a.abort();s?.signal&&(s.signal.aborted?a.abort():s.signal.addEventListener("abort",l,{once:!0}));let c=setTimeout(()=>a.abort(),i);try{let u;try{u=await ne(`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(e)}/account`,{method:"PATCH",headers:{"Content-Type":"application/json","x-checkout-session-token":t},body:JSON.stringify(r),signal:a.signal},H,y=>{this.telemetryHooks?.onRetry?.("account_snapshot",y),this.directTelemetry?.log({name:"operation.retry",stage:"processing",requestCategory:"account_snapshot",attempt:y})})}finally{clearTimeout(c),s?.signal?.removeEventListener("abort",l)}if(!u.ok)throw await N(u,"Failed to persist account snapshot");this.directTelemetry?.performance({stage:"processing",durationMs:Math.max(0,this.telemetryTimestamp()-o),durationMode:"machine",requestCategory:"account_snapshot",statusClass:"2xx"})}catch(u){throw this.reportAccountSnapshotFailure(u,s?.telemetryMode??"blocking"),u}}async createSessionIntent(e,t,r,s){if(!t)throw new d.FloPayError("createSessionIntent requires the checkout session nonce.","validation_error",{code:"MissingCheckoutSessionToken",param:"nonce"});let o=r,i=o.paymentMethodType,a=typeof i=="string"&&i.trim().toLowerCase()==="card",l=typeof i=="string"&&i.length>0&&!a&&(typeof o.paymentMethodId=="string"||o.paymentMethodId===null),c=o.provider==="stripe"&&(o.paymentMethodCategory==="wallet"||o.paymentMethodCategory==="apm")&&(o.intentKind==="payment"||o.intentKind==="setup"),u=o.provider==="paypal"&&o.paymentMethodCategory==="wallet"&&o.paymentMethodType==="paypal"&&o.paymentMethodId===null&&o.intentKind==="payment";if(!l||!c&&!u)throw new d.FloPayError("Only wallet, APM, and PayPal session intents are supported.","validation_error",{code:"InvalidSessionIntentRequest"});let y=o.authorizationAttemptId;if(y!==void 0&&!(0,d.isUuidV4)(y))throw new d.FloPayError("authorizationAttemptId must be a v4 UUID identifying one buyer authorization attempt.","validation_error",{code:"InvalidAuthorizationAttemptId",param:"authorizationAttemptId"});let b=(0,d.isUuidV4)(y)?y:(0,d.randomUuidV4)();this.beginDirectTelemetryCheckout(e);let g=this.telemetryTimestamp();this.directTelemetry?.log({name:"payment.intent.started",stage:"processing",requestCategory:"intent_create"});let E={"Content-Type":"application/json","x-checkout-session-token":t,[d.IDEMPOTENCY_KEY_HEADER]:s?.idempotencyKey||b},k=!1;try{let P=await fetch(`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(e)}/intents`,{method:"POST",headers:E,body:JSON.stringify({...r,authorizationAttemptId:b}),signal:s?.signal});if(!P.ok)throw k=!0,this.directTelemetry?.error({errorCode:"PAYMENT_PROCESSING_FAILED",stage:"processing",requestCategory:"intent_create",statusClass:D(P.status)}),await N(P,"Failed to create checkout intent");let O=(await P.json()).data;if(!O||typeof O!="object")throw new d.FloPayError("Invalid checkout intent response.","api_error",{code:"InvalidSessionIntentResponse"});let p=O,z=p.paymentMethodType,q=(p.paymentMethodCategory==="wallet"||p.paymentMethodCategory==="apm")&&typeof z=="string"&&z.trim().toLowerCase()!=="card"&&(typeof p.paymentMethodId=="string"||p.paymentMethodId===null)&&typeof p.providerObjectId=="string",S=p.provider==="stripe"&&(p.intentKind==="payment"||p.intentKind==="setup")&&typeof p.clientSecret=="string",j=p.provider==="paypal"&&p.paymentMethodCategory==="wallet"&&p.paymentMethodType==="paypal"&&p.paymentMethodId===null&&p.intentKind==="payment"&&(p.providerObjectType==="order"||p.providerObjectType==="subscription")&&p.clientSecret===null,ie=p.provider===r.provider&&p.paymentMethodCategory===r.paymentMethodCategory&&p.paymentMethodType===r.paymentMethodType&&p.paymentMethodId===r.paymentMethodId&&p.intentKind===r.intentKind;if(!q||!S&&!j||!ie)throw new d.FloPayError("Invalid checkout intent response.","api_error",{code:"InvalidSessionIntentResponse"});return this.directTelemetry?.log({name:"payment.intent.completed",stage:"processing",requestCategory:"intent_create",statusClass:D(P.status)}),this.directTelemetry?.performance({stage:"processing",durationMs:Math.max(0,this.telemetryTimestamp()-g),durationMode:"machine",requestCategory:"intent_create",statusClass:D(P.status)}),p}catch(P){throw k||this.reportDirectFailure(P,"PAYMENT_PROCESSING_FAILED","processing","intent_create"),P}}async reportSessionIntentDecline(e,t,r,s){if(!t)throw new d.FloPayError("reportSessionIntentDecline requires the checkout session nonce.","validation_error",{code:"MissingCheckoutSessionToken",param:"nonce"});let o=r,i=o.providerDeclineReason,a=o.paymentMethodType,l=typeof i=="string"&&/^[a-z0-9][a-z0-9_.:-]{0,63}$/i.test(i)&&!/^(?:pm|pi|seti|tok|src|cus|sess|sk|pk)_/i.test(i),c=typeof a=="string"&&a.length>0&&a.trim().toLowerCase()!=="card"&&l,u=o.provider==="stripe"&&(o.paymentMethodCategory==="wallet"||o.paymentMethodCategory==="apm"),y=o.provider==="paypal"&&o.paymentMethodCategory==="wallet"&&o.paymentMethodType==="paypal";if(!c||!u&&!y)throw new d.FloPayError("Invalid non-card decline classification.","validation_error",{code:"InvalidSessionIntentDeclineRequest"});let b=y?{provider:"paypal",paymentMethodCategory:"wallet",paymentMethodType:"paypal",providerDeclineReason:i}:{provider:"stripe",paymentMethodCategory:o.paymentMethodCategory,paymentMethodType:o.paymentMethodType,providerDeclineReason:i},g=await fetch(`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(e)}/intents/decline`,{method:"POST",headers:{"Content-Type":"application/json","x-checkout-session-token":t},body:JSON.stringify(b),signal:s?.signal});if(!g.ok)throw await N(g,"Failed to report checkout decline")}async getPaymentsByEmail(e,t){this.beginDirectTelemetryOperation();let r=this.telemetryTimestamp();this.directTelemetry?.log({name:"operation.recovery.started",stage:"recovery",requestCategory:"other",paymentMethodCategory:"saved"});let s=t?.page??1,o=t?.limit??1,i=new URLSearchParams({email:e,page:String(s),limit:String(o),sortField:"createdAt",sortDirection:"DESC"});try{let a=await fetch(`${this.baseUrl}/v1/payments?${i.toString()}`,{method:"GET",signal:t?.signal,keepalive:!0});if(!a.ok)throw new d.FloPayError("Failed to fetch payments","api_error",{statusCode:a.status});let l=await a.json();return this.directTelemetry?.log({name:"operation.recovery.completed",stage:"recovery",requestCategory:"other",paymentMethodCategory:"saved",statusClass:"2xx"}),this.directTelemetry?.performance({stage:"recovery",durationMs:Math.max(0,this.telemetryTimestamp()-r),durationMode:"machine",requestCategory:"other",paymentMethodCategory:"saved",statusClass:"2xx"}),l}catch(a){throw this.reportDirectFailure(a,"RECOVERY_FAILED","recovery","other","saved"),a}}async createAndFetchSession(e){this.beginDirectTelemetryOperation();let t=this.telemetryTimestamp();this.directTelemetry?.log({name:"session.create.started",stage:"session_create",requestCategory:"session_create"});try{let r=await this.createAndFetchSessionRequest(e,t);return this.adoptDirectTelemetryCheckout(r.data.session?.id),this.directTelemetry?.log({name:"session.request.completed",stage:"session_complete",requestCategory:"session_create",statusClass:"2xx"}),this.directTelemetry?.performance({stage:"session_create",durationMs:this.telemetryTimestamp()-t,durationMode:"machine",requestCategory:"session_create",statusClass:"2xx"}),r}catch(r){if(!(r instanceof d.FloPayError&&r.code==="session_auto_completed"))try{this.telemetryHooks?.onSessionCreateFailure?.(r)}catch{}if(r instanceof d.FloPayError&&r.type==="validation_error")this.directTelemetry?.terminal({outcome:"validation_rejected",stage:"session_create",requestCategory:"session_create"});else if(!(r instanceof d.FloPayError&&r.code==="session_auto_completed")){let s=r instanceof d.FloPayError?r.statusCode:void 0;this.directTelemetry?.error({errorCode:r instanceof Error&&r.name==="AbortError"?"REQUEST_TIMEOUT":r instanceof TypeError?"NETWORK_REQUEST_FAILED":"CHECKOUT_SESSION_CREATE_FAILED",stage:"session_create",requestCategory:"session_create",statusClass:r instanceof Error&&r.name==="AbortError"?"timeout":s?`${Math.floor(s/100)}xx`:"network_error"})}throw r}}async createAndFetchSessionRequest(e,t){let r=e.products??(0,d.foldIntoProducts)(e.items,e.subscriptions),s=(0,d.resolveSessionCurrency)(e.currency,e.items,e.subscriptions,r);if(!s)throw new d.FloPayError("currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.","validation_error",{code:"CurrencyRequired",param:"currency"});let o={clientId:e.clientId,checkoutVersion:d.SDK_VERSION,successUrl:e.successUrl,cancelUrl:e.cancelUrl,currency:s,checkoutMode:e.checkoutMode??"full",products:r.map(g=>(0,d.buildProductPayload)(g,s)),accountData:{userId:e.account.userId,firstName:e.account.firstName??null,lastName:e.account.lastName??null,email:e.account.email,country:e.account.country??null,gender:e.account.gender??null,city:e.account.city??null,state:e.account.state??null,zip:e.account.zip??null,addressLine1:e.account.addressLine1??null,addressLine2:e.account.addressLine2??null},couponCodes:e.couponCodes??[]};e.tokenizedData&&(o.tokenizedData=e.tokenizedData),e.tagsData&&(o.tagsData=e.tagsData),e.utmMetadata?.length&&(o.utmMetadata=e.utmMetadata),e.avsCheck!==void 0&&(o.avsCheck=e.avsCheck),e.checkoutType&&(o.checkoutType=e.checkoutType),e.checkoutLayout&&(o.checkoutLayout=e.checkoutLayout),e.avsConfig&&(o.avsConfig=e.avsConfig);let i={"Content-Type":"application/json",[d.FLO_SDK_VERSION_HEADER]:d.SDK_VERSION},a=(0,d.resolveIdempotencyKey)(e.idempotencyKey);a&&(i[d.IDEMPOTENCY_KEY_HEADER]=a);let l,c=!1;for(let g=0;;g++){if(l=await ne(`${this.baseUrl}/v1/checkouts/sessions?expand=true`,{method:"POST",headers:i,body:JSON.stringify(o)},H,k=>{this.telemetryHooks?.onRetry?.("session_create",k),this.directTelemetry?.log({name:"operation.retry",stage:"session_create",requestCategory:"session_create",attempt:k})}),!c){c=!0;let k=`${Math.floor(l.status/100)}xx`;this.directTelemetry?.log({name:"session.request.first_byte",stage:"session_first_byte",requestCategory:"session_create",statusClass:k}),this.directTelemetry?.performance({stage:"session_first_byte",durationMs:this.telemetryTimestamp()-t,durationMode:"machine",requestCategory:"session_create",statusClass:k})}if(l.status===204)throw new d.FloPayError("Session auto-completed \u2014 payment method already on file","api_error",{code:"session_auto_completed"});if(l.ok)break;let E=await N(l,"Failed to create checkout session");if(E.code===d.IDEMPOTENCY_IN_PROGRESS_CODE&&g<ft){try{this.telemetryHooks?.onRetry?.("session_create",g+1),this.directTelemetry?.log({name:"operation.retry",stage:"session_create",requestCategory:"session_create",attempt:g+1})}catch{}await ue(150*2**g);continue}throw E}let u=await l.json();if(u.data&&"gateways"in u.data){this.autoCacheDisplayData(u.data.uuid,e);let g=this.mergeCachedDisplayData(u.data),E=this.normalizeRawSession(g);return u.vault&&E.data.session&&(E.data.session.vault=this.toVaultBlock(u.vault)),{...E,autoProcessingError:u.autoProcessingError,autoProcessingAttempted:u.autoProcessingAttempted,autoProcessingPending:u.autoProcessingPending}}let y=u.data?.uuid;if(!y)throw new d.FloPayError("No session ID returned","api_error");return this.autoCacheDisplayData(y,e),this.adoptDirectTelemetryCheckout(y),{...await this.getUnifiedCheckoutSession(y),autoProcessingError:u.autoProcessingError,autoProcessingAttempted:u.autoProcessingAttempted,autoProcessingPending:u.autoProcessingPending}}async waitForCheckoutSessionCompletion(e,t){this.beginDirectTelemetryCheckout(e);let r=this.telemetryTimestamp();this.directTelemetry?.log({name:"operation.recovery.started",stage:"recovery",requestCategory:"session_read",paymentMethodCategory:"saved"});let s=t?.timeoutMs??pt,o=Date.now()+s,i=this.clampRetryAfterMs(t?.initialDelayMs??Ee),a=0;try{for(;;){let l=o-Date.now();if(l<=0)throw re();if(i>0){try{a+=1,this.telemetryHooks?.onRetry?.("session_read",a),this.directTelemetry?.log({name:"operation.retry",stage:"recovery",requestCategory:"session_read",paymentMethodCategory:"saved",attempt:a})}catch{}if(await ue(Math.min(i,l)),Date.now()>=o)throw re()}let c=await this.getUnifiedCheckoutSession(e,t?.nonce),u=c.data.session?.status;if(u==="complete"||u==="expired")return this.directTelemetry?.log({name:"operation.recovery.completed",stage:"recovery",requestCategory:"session_read",paymentMethodCategory:"saved"}),this.directTelemetry?.performance({stage:"recovery",durationMs:Math.max(0,this.telemetryTimestamp()-r),durationMode:"machine",requestCategory:"session_read",paymentMethodCategory:"saved"}),c;if(Date.now()>=o)throw re();i=this.clampRetryAfterMs(Math.max(i*2,ut))}}catch(l){throw l instanceof d.FloPayError&&l.code==="checkout_processing_timeout"&&this.reportDirectFailure(l,"RECOVERY_FAILED","recovery","session_read","saved"),l}}normalizeRawSession(e){let t=e.gateways??{},r=[],s={session:this.toCheckoutSession(e)},o=t.stripe;if(o?.publishableKey){r.push("stripe");let l=[e.stripeClientSecret,o.stripeClientSecret].find(c=>typeof c=="string"&&c.length>0);s.stripe={clientSecret:l??"",publishableKey:o.publishableKey??void 0,paypalPublishableKey:o.paypalPublishableKey??void 0,environment:o.environment,enabledPaymentMethods:Array.isArray(o.enabledPaymentMethods)?o.enabledPaymentMethods.filter(c=>typeof c=="string"):void 0}}let i=t.paypal;return i?.publishableKey&&(r.push("paypal"),s.paypal={publishableKey:i.publishableKey,environment:i.environment}),{providers:r,mode:"tokenize",data:s,raw:{data:e}}}toCheckoutSession(e){let t=e.products??[],r=typeof e.totalAmount=="number"&&Number.isFinite(e.totalAmount),s=t.reduce((c,u)=>c+(u.overrideAmount??u.totalAmount??0),0),o=r?e.totalAmount:s,i=Math.round(o*100),a=e.currency??t[0]?.currency??"USD",l=t.some(c=>c.type==="subscription")?"subscription":"payment";return{id:e.uuid,clientSecret:e.nonce,mode:l,status:this.toCheckoutSessionStatus(e.status),amount:i,currency:a,customer:{id:e.accountData.userId,email:e.accountData.email,firstName:e.accountData.firstName,lastName:e.accountData.lastName,country:e.accountData.country??void 0,city:e.accountData.city??void 0,state:e.accountData.state??void 0,zip:e.accountData.zip??void 0,gender:e.accountData.gender??void 0,line1:e.accountData.addressLine1??void 0,line2:e.accountData.addressLine2??void 0},metadata:{},checkoutMode:e.checkoutMode,providerPaymentMethodId:typeof e.providerPaymentMethodId=="string"?e.providerPaymentMethodId:null,products:t.map(c=>({...c,totalAmount:typeof c.totalAmount=="number"?c.totalAmount:void 0,overrideAmount:typeof c.overrideAmount=="number"?c.overrideAmount:null,currency:typeof c.currency=="string"?c.currency:void 0,metadata:c.metadata??null})),successUrl:e.successUrl,cancelUrl:e.cancelUrl,coupons:e.coupons,subtotalAmount:e.subtotalAmount,discountAmount:e.discountAmount,totalAmount:e.totalAmount,createdAt:e.createdAt,gateways:e.gateways,accountData:e.accountData,tagsData:e.tagsData}}toVaultBlock(e){return{html:typeof e.html=="string"?e.html:void 0,url:typeof e.url=="string"?e.url:void 0,messageToken:typeof e.messageToken=="string"?e.messageToken:void 0,expectedOrigin:typeof e.expectedOrigin=="string"?e.expectedOrigin:void 0}}toCheckoutSessionStatus(e){return e==="completed"?"complete":e==="expired"?"expired":"open"}async resolveProcessResponse(e,t,r){if(e.status!==202)return e;let s=await e.json().catch(()=>null),o=this.toCheckoutProcessingPending(s,e,t),i=await this.waitForCheckoutSessionCompletion(o.sessionId,{initialDelayMs:o.retryAfterMs,timeoutMs:r?.pollTimeoutMs,nonce:r?.nonce});if(i.data.session?.status==="complete")return new Response(null,{status:204,statusText:"No Content"});throw i.data.session?.status==="expired"?new d.FloPayError("Checkout session has expired.","api_error",{code:"checkout_session_expired"}):re()}toCheckoutProcessingPending(e,t,r){let s=t.headers.get("Retry-After"),o=s===null||s.trim()===""?void 0:Number(s),i=o!==void 0&&Number.isFinite(o)?o*1e3:void 0;return{type:"checkout_processing",sessionId:V(e,"sessionId")??r,retryAfterMs:this.clampRetryAfterMs(gt(e,"retryAfterMs")??i??Ee),statusUrl:V(e,"statusUrl"),sessionUrl:V(e,"sessionUrl")}}clampRetryAfterMs(e){return Math.max(0,Math.min(e,mt))}autoCacheDisplayData(e,t){if(!e)return;let r=t.products??(0,d.foldIntoProducts)(t.items,t.subscriptions);if(r.length===0&&!t.currency)return;let s=t.products!==void 0,o=(0,d.resolveSessionCurrency)(t.currency,s?void 0:t.items,s?void 0:t.subscriptions,r);x(e,{currency:o??void 0,products:r.map(i=>({code:i.code??i.providerItemId??i.providerPlanId,type:i.type,name:i.name??i.itemName??i.providerItemName??i.subscriptionName??i.providerPlanName??null,totalAmount:i.totalAmount,overrideAmount:i.overrideAmount,currency:i.currency??o??void 0}))})}mergeCachedDisplayData(e){let t=ee(e.uuid),r=new Map,s=i=>i?`code:${i}`:void 0;for(let i of t?.products??[]){let a=s(i.code);a&&r.set(a,i)}let o=(e.products??[]).map(i=>{let a=s(i.code),l=a?r.get(a):void 0;return{...i,name:i.name??l?.name??null,totalAmount:i.totalAmount??l?.totalAmount,overrideAmount:i.overrideAmount??l?.overrideAmount,currency:i.currency??l?.currency}});return{...e,currency:e.currency??t?.currency,products:o}}};function Re(n,e){let t=A;return new t(n,e)}function Se(n){return{payment:"payment",address:"address"}[n]}function Ae(n){switch(n){case"night":return"night";case"flat":return"flat";default:return"stripe"}}function pe(n){return n?JSON.stringify(n.map(e=>e.trim().toLowerCase())):null}function Ie(n){let e=n;return{mount(t){e.mount(t)},unmount(){e.unmount()},update(t){e.update(t)},on(t,r){e.on?.(t,r)},off(t,r){e.off?.(t,r)},destroy(){e.destroy()}}}function vt(n){return{billing_details:{...n.email?{email:n.email}:{},...n.name?{name:n.name}:{},...n.address?{address:{...n.address.country?{country:n.address.country}:{},...n.address.postal_code?{postal_code:n.address.postal_code}:{},...n.address.city?{city:n.address.city}:{},...n.address.line1?{line1:n.address.line1}:{},...n.address.line2?{line2:n.address.line2}:{},...n.address.state?{state:n.address.state}:{}}}:{}}}}var K=class{constructor(){this.name="stripe";this.stripe=null;this.elements=null;this.appliedAppearanceKey=null;this.appliedPaymentMethodTypesKey=null;this.appliedClientSecret=null;this.verifiedClientSecret=null;this.verifiedPaymentMethodTypesKey=null}async initialize(e){if(typeof window>"u")return;let t=await(0,xe.loadStripe)(e.publishableKey,{locale:e.locale??"auto"});if(!t)throw new h.FloPayError("Failed to initialize Stripe. Check your publishable key.","authentication_error");this.stripe=t}getElements(e){if(!this.stripe)throw new h.FloPayError("StripeAdapter not initialized. Call initialize() first.","api_error");let t=e?.appearance?{theme:Ae(e.appearance.theme),variables:e.appearance.variables,rules:e.appearance.rules}:void 0,r=t?JSON.stringify(t):null,s=pe(e?.paymentMethodTypes),o=e?.clientSecret??null;if(this.elements&&s&&(s!==this.appliedPaymentMethodTypesKey||o!==this.appliedClientSecret)&&(this.elements=null,this.appliedAppearanceKey=null,this.appliedPaymentMethodTypesKey=null,this.appliedClientSecret=null,this.verifiedClientSecret=null,this.verifiedPaymentMethodTypesKey=null),this.elements)r!==this.appliedAppearanceKey&&(this.elements.update({appearance:t??{}}),this.appliedAppearanceKey=r);else{let i,a=e?.amount??0,l=(e?.currency??"usd").toLowerCase(),c=e?.paymentMethodCreation??"manual";e?.clientSecret?i={clientSecret:e.clientSecret}:a>0?(i={mode:"payment",amount:a,currency:l,paymentMethodCreation:c},e?.setupFutureUsage&&(i.setupFutureUsage=e.setupFutureUsage)):i={mode:"setup",currency:l,paymentMethodCreation:c},!e?.clientSecret&&e?.paymentMethodTypes&&(i.paymentMethodTypes=e.paymentMethodTypes),t&&(i.appearance=t),this.elements=this.stripe.elements(i),this.appliedAppearanceKey=r,this.appliedPaymentMethodTypesKey=s,this.appliedClientSecret=o,this.verifiedClientSecret=null,this.verifiedPaymentMethodTypesKey=null}return this.elements}async assertClientSecretPaymentMethods(e,t){if(!this.stripe)throw new h.FloPayError("StripeAdapter not initialized. Call initialize() first.","api_error");let r,s=!1;if((0,h.isSetupIntentClientSecret)(e)){let{setupIntent:l,error:c}=await this.stripe.retrieveSetupIntent(e);r=l,s=!!c}else{let{paymentIntent:l,error:c}=await this.stripe.retrievePaymentIntent(e);r=l,s=!!c}let o=r?.payment_method_types;if(s||!Array.isArray(o))throw new h.FloPayError("Unable to verify the payment methods configured for this client secret.","api_error",{param:"clientSecret"});let i=new Set(t.map(l=>l.toLowerCase()));if(o.some(l=>typeof l!="string"||!i.has(l.trim().toLowerCase()))||o.length===0)throw new h.FloPayError("The client-secret intent must enable only declared non-card payment methods.","validation_error",{param:"clientSecret"})}async createElement(e,t){let r=t;if(e==="payment"){let l=t.paymentMethodTypes?.map(c=>c.trim()).filter(c=>c&&c.toLowerCase()!=="card");if(!l?.length)throw new h.FloPayError("At least one supported non-card payment method is required.","validation_error",{param:"paymentMethodTypes"});if(r={...t,paymentMethodTypes:l},r.clientSecret){let c=pe(l);this.elements&&this.appliedClientSecret===r.clientSecret&&this.appliedPaymentMethodTypesKey===c&&this.verifiedClientSecret===r.clientSecret&&this.verifiedPaymentMethodTypesKey===c||await this.assertClientSecretPaymentMethods(r.clientSecret,l)}}let s=this.getElements(r);e==="payment"&&r.clientSecret&&(this.verifiedClientSecret=r.clientSecret,this.verifiedPaymentMethodTypesKey=pe(r.paymentMethodTypes));let o=Se(e),i={};r.layout&&(i.layout=r.layout),r.defaultValues&&(i.defaultValues=r.defaultValues),r.readOnly&&(i.readOnly=r.readOnly),r.mode&&(i.mode=r.mode);let a=s.create(o,i);return Ie(a)}getElement(e){if(!this.elements)return null;let t=Se(e),r=this.elements.getElement(t);return r?Ie(r):null}async submitElements(){if(!this.stripe||!this.elements)return{error:new h.FloPayError("Stripe not initialized","api_error")};let{error:e}=await this.elements.submit();return e?{error:new h.FloPayError(e.message??"Validation failed","validation_error")}:{}}async confirmPayment(e){if(!this.stripe||!this.elements)throw new h.FloPayError("StripeAdapter not initialized or no elements created.","api_error");let t=e.billingDetails,r=t?vt(t):void 0,{error:s,paymentIntent:o}=await this.stripe.confirmPayment({elements:this.elements,clientSecret:e.clientSecret,confirmParams:{return_url:e.returnUrl??window.location.href,...r?{payment_method_data:r}:{}},redirect:"if_required"});return s?{status:"failed",error:new h.FloPayError(s.message??"Payment failed","api_error",{code:s.code,declineCode:s.decline_code})}:o?{status:{succeeded:"succeeded",processing:"processing",requires_action:"requires_action",requires_payment_method:"failed",canceled:"failed"}[o.status]??"failed",paymentIntentId:o.id,paymentMethodId:this.extractPaymentMethodId(o.payment_method)}:{status:"failed",error:new h.FloPayError("No payment intent returned","api_error")}}extractPaymentMethodId(e){if(typeof e=="string"&&e.startsWith("pm_"))return e;if(e&&typeof e=="object"&&typeof e.id=="string")return e.id}async confirmPayPalPayment(e){if(!this.stripe)return{status:"failed",error:new h.FloPayError("Stripe not initialized","api_error")};let t=e.billingApiUrl.replace(/\/+$/,"");if(this.elements){let{error:o}=await this.elements.submit();if(o)return{status:"failed",error:new h.FloPayError(o.message??"PayPal payment failed","validation_error",{code:o.code})}}let r;try{let o=await new A(t).createSessionIntent(e.sessionId,e.nonce??"",{provider:"stripe",paymentMethodCategory:"wallet",paymentMethodType:"paypal",paymentMethodId:null,intentKind:"payment"});if(o.provider!=="stripe")throw new h.FloPayError("Invalid provider returned for PayPal intent","api_error");r=o.clientSecret}catch(o){return{status:"failed",error:o instanceof h.FloPayError?o:new h.FloPayError("Failed to create PayPal payment intent","api_error")}}let{error:s}=await this.stripe.confirmPayment({clientSecret:r,elements:this.elements??void 0,confirmParams:{return_url:e.returnUrl}});if(s){if(e.nonce)try{await new A(t).reportSessionIntentDecline(e.sessionId,e.nonce,{provider:"stripe",paymentMethodCategory:"wallet",paymentMethodType:"paypal",providerDeclineReason:s.code??"provider_declined"})}catch{}return{status:"failed",error:new h.FloPayError(s.message??"PayPal payment failed","api_error",{code:s.code})}}return{status:"processing"}}async resumePayPalPayment(){if(!this.stripe||typeof window>"u")return null;let e=new URLSearchParams(window.location.search),t=e.get("payment_intent"),r=e.get("payment_intent_client_secret"),s=e.get("redirect_status");if(!t||!r)return null;if(s==="failed")return{status:"failed",error:new h.FloPayError("PayPal payment was declined. Please try again.","api_error")};let{paymentIntent:o,error:i}=await this.stripe.retrievePaymentIntent(r);if(i)return{status:"failed",error:new h.FloPayError(i.message??"Failed to retrieve PayPal payment","api_error")};if(o&&(o.status==="requires_capture"||o.status==="succeeded")){let a=typeof o.payment_method=="string"?o.payment_method:o.payment_method?.id,l=new URL(window.location.href);return l.searchParams.delete("payment_intent"),l.searchParams.delete("payment_intent_client_secret"),l.searchParams.delete("redirect_status"),window.history.replaceState({},"",l.toString()),{status:o.status,paymentIntentId:o.id,paymentMethodId:a}}return{status:"failed",error:new h.FloPayError("PayPal payment was not completed. Please try again.","api_error")}}getRawProvider(){return this.stripe}createPayPalElements(e){if(!this.stripe)return null;let t={mode:"payment",amount:e.amount??0,currency:(e.currency??"usd").toLowerCase(),captureMethod:"manual"};return e.setupFutureUsage&&(t.setupFutureUsage=e.setupFutureUsage),e.appearance&&(t.appearance={theme:Ae(e.appearance.theme),variables:e.appearance.variables,rules:e.appearance.rules}),this.stripe.elements(t)}destroy(){this.elements=null,this.appliedAppearanceKey=null,this.appliedPaymentMethodTypesKey=null,this.appliedClientSecret=null,this.verifiedClientSecret=null,this.verifiedPaymentMethodTypesKey=null,this.stripe=null}};var w=require("@flopay/shared");var De=require("@flopay/shared"),B=class{constructor(e,t){this.elementMap=new Map;this.provider=e,this.baseOptions=t??{}}async create(e,t){let r={...this.baseOptions,...t};if(e==="payment"){let a=r.paymentMethodTypes?.map(l=>l.trim()).filter(l=>l&&l.toLowerCase()!=="card");if(!a?.length)throw new De.FloPayError("At least one supported non-card payment method is required.","validation_error",{param:"paymentMethodTypes"});r.paymentMethodTypes=a}let s=this.provider.getElement(e);if(s)return this.elementMap.set(e,s),s;let o=this.elementMap.get(e);o&&o.destroy();let i=await this.provider.createElement(e,r);return this.elementMap.set(e,i),i}getElement(e){return this.elementMap.get(e)??null}async submit(){return{}}destroy(){for(let e of this.elementMap.values())e.destroy();this.elementMap.clear()}};var C=require("@flopay/shared");var me="flopay-vault",Fe={ready:["vault.widget.ready","vault_ready"],submitting:["vault.submission.started","vault_submit"],blocked:["operation.state_transition","vault_submit"],action_required:["vault.action.required","three_ds_handoff"]};function $(n){let e={class:n.class,stage:n.stage};"code"in n&&(e.code=n.code),"provider"in n&&n.provider&&(e.provider=n.provider),"paymentMethodCategory"in n&&n.paymentMethodCategory&&(e.paymentMethodCategory=n.paymentMethodCategory),"outcome"in n&&(e.outcome=n.outcome),"durationMs"in n&&n.durationMs!==void 0&&(e.durationMs=n.durationMs),"durationMode"in n&&n.durationMode&&(e.durationMode=n.durationMode);try{globalThis.Sentry?.addBreadcrumb?.({category:"flopay.telemetry",level:n.class==="technical_error"?"error":"info",message:n.class==="lifecycle"?n.name:n.class==="technical_error"?n.code:n.class==="expected_outcome"?n.outcome:"sdk.performance",data:e})}catch{}}function oe(n,e){return(0,C.buildTelemetryLogEvent)({eventId:"11111111-1111-4111-8111-111111111111",name:n,stage:e,sequence:0,provider:"pcivault",paymentMethodCategory:"card"})}function Oe(n){return n?{errorCode:"VAULT_SUBMIT_FAILED",stage:"vault_submit"}:{errorCode:"VAULT_LOAD_FAILED",stage:"vault_mount"}}function Tt(n,e){if(n==="complete"||n==="decline")return(0,C.buildTelemetryTerminalEvent)({eventId:"22222222-2222-4222-8222-222222222222",outcome:n==="complete"?"payment_succeeded":"payment_declined",sequence:0,provider:"pcivault",paymentMethodCategory:"card"});if(n==="error"){let s=Oe(e);return(0,C.buildTelemetryErrorEvent)({eventId:"33333333-3333-4333-8333-333333333333",...s,sequence:0,provider:"pcivault",paymentMethodCategory:"card"})}let[t,r]=Fe[n];return oe(t,r)}function _t(n){if(typeof n!="object"||n===null)return!1;let e=n;return e.source===me&&(e.type==="ready"||e.type==="submitting"||e.type==="blocked"||e.type==="complete"||e.type==="decline"||e.type==="error"||e.type==="action_required")}function wt(n){if(typeof n!="object"||n===null)return!1;let e=n;return e.source===me&&e.type==="validation"&&Array.isArray(e.messages)}function Pt(n){if(typeof n!="object"||n===null)return!1;let e=n;return e.source===me&&e.type==="resize"&&typeof e.height=="number"&&Number.isFinite(e.height)}var F=class{constructor(e={},t){this.provider="pcivault";this.container=null;this.messageHandler=null;this.actionOverlay=null;this.threeDsReturnHandler=null;this.messageToken=null;this.expectedOrigin=null;this.theme=null;this.submitGateBlocked=!1;this.cardFieldOrder=null;this.cardAutoFocus=!0;this.captureRequestedAt=0;this.vaultReadyReported=!1;this.submissionStarted=!1;this.submissionStartedAt=null;this.listeners=new Map;this.config=e,this.ownsTelemetryReporter=!t&&e.telemetry!==!1,this.telemetryReporter=t?.reporter??(this.ownsTelemetryReporter?new v({billingApiUrl:(0,C.resolveBillingApiUrl)(),sdkVersion:C.SDK_VERSION}):void 0),this.requestedAt=t?.requestedAt}async mount(e,t){this.ownsTelemetryReporter&&!this.telemetryReporter&&(this.telemetryReporter=new v({billingApiUrl:(0,C.resolveBillingApiUrl)(),sdkVersion:C.SDK_VERSION}));let r=this.telemetryReporter?.now?.()??_();if(this.captureRequestedAt=this.requestedAt??r,this.vaultReadyReported=!1,this.submissionStarted=!1,this.submissionStartedAt=null,typeof window>"u"||typeof document>"u")throw this.reportVaultLoadFailure(),new C.FloPayError("The vault card form is only available in the browser.","api_error",{code:"card_capture_no_window"});if(!t?.html||!t.html.trim())throw this.reportVaultLoadFailure(),new C.FloPayError("No vault capture widget HTML was provided to mount the secure card form.","api_error",{code:"card_capture_no_widget_html"});this.container=e,this.messageToken=t.messageToken??null,this.expectedOrigin=t.expectedOrigin??this.config.expectedOrigin??null,this.theme=t.theme??null;try{this.attachMessageListener(),this.injectWidget(e,t.html)}catch(s){throw this.reportVaultLoadFailure(),s}this.postTheme(),this.postSubmitGate(),this.postCardFieldOrder(),$(oe("vault.widget.mounted","vault_mount")),this.telemetryReporter?.log({name:"vault.widget.mounted",stage:"vault_mount",provider:"pcivault",paymentMethodCategory:"card"}),this.emit("ready",{sessionId:this.config.sessionId})}reportVaultLoadFailure(){this.telemetryReporter?.error({errorCode:"VAULT_LOAD_FAILED",stage:"vault_mount",provider:"pcivault",paymentMethodCategory:"card"})}on(e,t){let r=this.listeners.get(e);return r||(r=new Set,this.listeners.set(e,r)),r.add(t),()=>{this.listeners.get(e)?.delete(t)}}unmount(){this.hideActionRequiredOverlay(),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.container&&(this.container.replaceChildren(),this.container=null),this.messageToken=null,this.expectedOrigin=null,this.ownsTelemetryReporter&&(this.telemetryReporter?.destroy(),this.telemetryReporter=void 0)}injectWidget(e,t){e.innerHTML=t;let r=Array.from(e.querySelectorAll("script"));for(let s of r){let o=document.createElement("script");for(let i of Array.from(s.attributes))o.setAttribute(i.name,i.value);o.text=s.text,s.replaceWith(o)}}attachMessageListener(){if(this.messageHandler)return;let e=t=>{if(this.expectedOrigin&&t.origin!==this.expectedOrigin)return;let r=t.data;if(Pt(r)){if(this.messageToken&&r.messageToken!==this.messageToken)return;this.applyHeight(r.height);return}if(wt(r)){if(this.messageToken&&r.messageToken!==this.messageToken)return;let a=r.messages.filter(l=>typeof l=="string"&&l.trim()).join(" ");this.emit("validation",{sessionId:this.config.sessionId,message:a||void 0});return}if(!_t(r)||this.messageToken&&r.messageToken!==this.messageToken)return;let s=this.config.sessionId,o=typeof r.sessionId=="string"?r.sessionId:void 0;if(s&&o&&o!==s||(r.type==="complete"||r.type==="decline")&&s&&o!==s)return;let i={sessionId:r.sessionId??this.config.sessionId,intentId:r.intentId,declineReason:r.declineReason,message:r.message,nextActionRedirectUrl:r.nextActionRedirectUrl};r.type==="submitting"&&(this.submissionStarted=!0,this.submissionStartedAt=this.telemetryReporter?.now?.()??_()),$(Tt(r.type,this.submissionStarted)),this.reportOutcome(r.type),r.type==="ready"&&(this.postTheme(),this.postSubmitGate(),this.postCardFieldOrder()),r.type==="action_required"&&r.nextActionRedirectUrl&&this.showActionRequiredOverlay(r.nextActionRedirectUrl),(r.type==="complete"||r.type==="decline"||r.type==="error"||r.type==="submitting")&&this.hideActionRequiredOverlay(),this.emit(r.type,i)};this.messageHandler=e,window.addEventListener("message",e)}reportOutcome(e){let t=this.telemetryReporter;if(!t)return;if(e==="ready"&&!this.vaultReadyReported&&(this.vaultReadyReported=!0,t.performance({stage:"vault_ready",durationMs:Math.max(0,t.now()-this.captureRequestedAt),durationMode:"machine",provider:"pcivault",paymentMethodCategory:"card"})),e==="complete"||e==="decline"){t.log({name:"vault.terminal",stage:"completion",provider:"pcivault",paymentMethodCategory:"card"}),t.terminal({outcome:e==="complete"?"payment_succeeded":"payment_declined",provider:"pcivault",paymentMethodCategory:"card"});return}if(e==="error"){let o=Oe(this.submissionStarted);t.log({name:"vault.terminal",stage:o.stage,provider:"pcivault",paymentMethodCategory:"card"}),t.error({...o,provider:"pcivault",paymentMethodCategory:"card"});return}if(e==="action_required"){t.log({name:"vault.action.required",stage:"three_ds_handoff",provider:"pcivault",paymentMethodCategory:"card"}),t.log({name:"vault.three_ds.handoff",stage:"three_ds_handoff",provider:"pcivault",paymentMethodCategory:"card"});let o=this.submissionStartedAt;this.submissionStartedAt=null,o!==null&&t.performance({stage:"three_ds_handoff",durationMs:Math.max(0,t.now()-o),durationMode:"machine",provider:"pcivault",paymentMethodCategory:"card"}),t.terminal({outcome:"action_required",stage:"three_ds_handoff",provider:"pcivault",paymentMethodCategory:"card"});return}let[r,s]=Fe[e];t.log({name:r,stage:s,provider:"pcivault",paymentMethodCategory:"card"})}applyTheme(e){this.theme=e,this.postTheme()}postTheme(){if(!this.theme||!this.container)return;let t=this.container.querySelector("iframe")?.contentWindow;if(t)try{t.postMessage({source:"flopay-vault-host",type:"theme",theme:this.theme},"*")}catch{}}setSubmitGate(e){this.submitGateBlocked=e,this.postSubmitGate()}postSubmitGate(){if(!this.container)return;let t=this.container.querySelector("iframe")?.contentWindow;if(t)try{t.postMessage({source:"flopay-vault-host",type:"gate",blocked:this.submitGateBlocked},"*")}catch{}}setCardFieldOrder(e,t){this.cardFieldOrder=e,this.cardAutoFocus=t,this.postCardFieldOrder()}postCardFieldOrder(){if(!this.container)return;let t=this.container.querySelector("iframe")?.contentWindow;if(t)try{t.postMessage({source:"flopay-vault-host",type:"fieldOrder",order:this.cardFieldOrder,autoFocus:this.cardAutoFocus},"*")}catch{}}emit(e,t){for(let r of this.listeners.get(e)??[])r(t)}showActionRequiredOverlay(e){if(typeof document>"u")return;if(this.actionOverlay){let i=this.actionOverlay.querySelector("iframe");i instanceof HTMLIFrameElement&&(i.src=e);return}let t=document.createElement("div");t.setAttribute("data-flopay-action-required","1"),t.style.cssText=["position:fixed","inset:0","z-index:2147483647","background:rgba(15,23,42,0.6)","display:flex","align-items:center","justify-content:center","padding:16px"].join(";");let r=document.createElement("iframe");r.setAttribute("title","Card authentication"),r.setAttribute("allow","payment"),r.style.cssText=["width:min(100%,460px)","height:min(100%,640px)","border:0","border-radius:12px","background:#fff","box-shadow:0 12px 30px rgba(0,0,0,0.35)"].join(";"),r.src=e,t.appendChild(r);let s=document.createElement("button");s.type="button",s.setAttribute("aria-label","Close card authentication"),s.textContent="\xD7",s.style.cssText=["position:fixed","top:20px","right:20px","width:40px","height:40px","border:0","border-radius:9999px","background:#fff","color:#0f172a","font-size:28px","line-height:40px","cursor:pointer","box-shadow:0 4px 14px rgba(0,0,0,0.25)"].join(";"),s.addEventListener("click",()=>this.abandonActionRequiredOverlay()),t.appendChild(s),t.addEventListener("click",i=>{i.target===t&&this.abandonActionRequiredOverlay()});let o=i=>{if(i.source!==r.contentWindow)return;let a=i.data;if(!a||typeof a!="object")return;let l=a;l.source==="flopay-vault-3ds-return"&&($(oe("vault.three_ds.returned","three_ds_return")),this.telemetryReporter?.log({name:"vault.three_ds.returned",stage:"three_ds_return",provider:"pcivault",paymentMethodCategory:"card"}),this.hideActionRequiredOverlay(),this.postActionCompleted(l.status))};window.addEventListener("message",o),this.threeDsReturnHandler=o,document.body.appendChild(t),this.actionOverlay=t,$(oe("vault.three_ds.handoff","three_ds_handoff"))}postActionCompleted(e){if(!this.container)return;let r=this.container.querySelector("iframe")?.contentWindow;if(r)try{r.postMessage({source:"flopay-vault-host",type:"action_completed",status:typeof e=="string"?e:"unknown"},"*")}catch{}}abandonActionRequiredOverlay(){if(!this.actionOverlay)return;let e=(0,C.buildTelemetryTerminalEvent)({eventId:"77777777-7777-4777-8777-777777777777",outcome:"customer_abandoned",stage:"three_ds_handoff",sequence:0,provider:"pcivault",paymentMethodCategory:"card"});$(e),this.telemetryReporter?.terminal({outcome:"customer_abandoned",stage:"three_ds_handoff",provider:"pcivault",paymentMethodCategory:"card"}),this.hideActionRequiredOverlay(),this.postActionCompleted("abandoned")}hideActionRequiredOverlay(){this.threeDsReturnHandler&&(window.removeEventListener("message",this.threeDsReturnHandler),this.threeDsReturnHandler=null),this.actionOverlay&&(this.actionOverlay.parentNode?.removeChild(this.actionOverlay),this.actionOverlay=null)}applyHeight(e){let t=this.container?.querySelector("iframe");if(!t)return;let r=Math.max(0,Math.min(Math.ceil(e),2e3));t.style.height=`${r}px`}};function qe(n,e,t){let r=F;return new r(n,{reporter:e,requestedAt:t})}var Ue=Symbol.for("@flopay/js.telemetry.bridge.v1");function Le(n,e,t){let r=()=>e?.now()??t();Object.defineProperty(n,Ue,{configurable:!1,enumerable:!1,writable:!1,value:{error:o=>e?.error(o),log:o=>e?.log(o),performance:o=>e?.performance(o),terminal:o=>e?.terminal(o),now:r,elapsed:o=>Math.max(0,r()-o),setCheckoutContext:o=>e?.setCheckoutContext(o),beginCheckout:(o={})=>e?.beginCheckout(o)??r(),disable:()=>e?.disable()}})}function Ne(n){return n[Ue]}function ye(n){if(!n)return!1;let e=n.code?.toLowerCase()??"";return!!n.declineCode||e.includes("declin")}function bt(n){return n==="stripe"||n==="paypal"||n==="pcivault"?n:"other"}var G=class{constructor(e,t,r){this.currentElements=null;this.provider=e,this.config=t,this.telemetryReporter=r??new v({billingApiUrl:(0,w.resolveBillingApiUrl)(t.billingApiUrl),sdkVersion:w.SDK_VERSION,enabled:t.telemetry!==!1}),Le(this,this.telemetryReporter,_)}now(){return this.telemetryReporter?.now?.()??_()}elements(e){return this.currentElements&&this.currentElements.destroy(),this.currentElements=new B(this.provider,{appearance:this.config.appearance,...e}),this.currentElements}async submitElements(){return this.provider.submitElements()}cardCapture(e){let t=this.now();return this.telemetryReporter?.log({name:"vault.capture.requested",stage:"vault_request",provider:"pcivault",paymentMethodCategory:"card"}),this.telemetryReporter?qe({sessionId:e?.sessionId},this.telemetryReporter,t):new F({sessionId:e?.sessionId,telemetry:!1})}async confirmPayPalPayment(e){let t=this.now();this.telemetryReporter?.log({name:"payment.method.selected",stage:"processing",provider:"paypal",paymentMethodCategory:"paypal"}),this.telemetryReporter?.log({name:"payment.intent.started",stage:"processing",provider:"paypal",paymentMethodCategory:"paypal",requestCategory:"intent_create"});try{let r=await this.provider.confirmPayPalPayment(e);return this.telemetryReporter?.performance({stage:"processing",durationMs:this.now()-t,durationMode:"machine",provider:"paypal",paymentMethodCategory:"paypal"}),r.error||this.telemetryReporter?.log({name:"payment.intent.completed",stage:"processing",provider:"paypal",paymentMethodCategory:"paypal",requestCategory:"intent_create",statusClass:"2xx"}),r.status==="requires_action"?(this.telemetryReporter?.log({name:"provider.redirect.started",stage:"redirect",provider:"paypal",paymentMethodCategory:"paypal"}),this.telemetryReporter?.terminal({outcome:"action_required",stage:"redirect",provider:"paypal",paymentMethodCategory:"paypal"})):r.status==="succeeded"?this.telemetryReporter?.terminal({outcome:"payment_succeeded",provider:"paypal",paymentMethodCategory:"paypal"}):r.status!=="processing"&&(ye(r.error)?this.telemetryReporter?.terminal({outcome:"payment_declined",provider:"paypal",paymentMethodCategory:"paypal"}):r.error?.type==="validation_error"?this.telemetryReporter?.terminal({outcome:"validation_rejected",provider:"paypal",paymentMethodCategory:"paypal"}):r.error&&this.telemetryReporter?.error({errorCode:"PAYMENT_PROCESSING_FAILED",stage:"processing",provider:"paypal",paymentMethodCategory:"paypal",requestCategory:"intent_create"})),r}catch(r){throw this.telemetryReporter?.performance({stage:"processing",durationMs:this.now()-t,durationMode:"machine",provider:"paypal",paymentMethodCategory:"paypal"}),this.telemetryReporter?.error({errorCode:"NETWORK_REQUEST_FAILED",stage:"processing",provider:"paypal",paymentMethodCategory:"paypal",requestCategory:"intent_create",statusClass:"network_error"}),r}}async resumePayPalPayment(){let e=this.now();try{let t=await this.provider.resumePayPalPayment();return t===null?null:(this.telemetryReporter?.log({name:"provider.redirect.resumed",stage:"redirect_resume",provider:"paypal",paymentMethodCategory:"paypal"}),this.telemetryReporter?.performance({stage:"redirect_resume",durationMs:this.now()-e,durationMode:"machine",provider:"paypal",paymentMethodCategory:"paypal"}),t.status==="succeeded"?this.telemetryReporter?.terminal({outcome:"payment_succeeded",provider:"paypal",paymentMethodCategory:"paypal"}):ye(t.error)?this.telemetryReporter?.terminal({outcome:"payment_declined",provider:"paypal",paymentMethodCategory:"paypal"}):t.error?.type==="validation_error"?this.telemetryReporter?.terminal({outcome:"validation_rejected",provider:"paypal",paymentMethodCategory:"paypal"}):t.error&&this.telemetryReporter?.error({errorCode:"REDIRECT_RESUME_FAILED",stage:"redirect_resume",provider:"paypal",paymentMethodCategory:"paypal"}),t)}catch(t){throw this.telemetryReporter?.performance({stage:"redirect_resume",durationMs:this.now()-e,durationMode:"machine",provider:"paypal",paymentMethodCategory:"paypal"}),this.telemetryReporter?.error({errorCode:"REDIRECT_RESUME_FAILED",stage:"redirect_resume",provider:"paypal",paymentMethodCategory:"paypal"}),t}}async confirmPayment(e){if(e.paymentMethodCategory!=="wallet"&&e.paymentMethodCategory!=="apm"||!e.paymentMethodType?.trim()||e.paymentMethodType.trim().toLowerCase()==="card")throw new w.FloPayError("A supported non-card payment method is required.","validation_error",{param:"paymentMethodType"});let t=this.now(),r=bt(this.provider.name);this.telemetryReporter?.log({name:"payment.processing.started",stage:"processing",provider:r,paymentMethodCategory:e.paymentMethodCategory});try{let s=await this.provider.confirmPayment(e),o=this.now()-t;return this.telemetryReporter?.performance({stage:"processing",durationMs:o,durationMode:"machine",provider:r,paymentMethodCategory:e.paymentMethodCategory}),s.status==="succeeded"?this.telemetryReporter?.terminal({outcome:"payment_succeeded",provider:r,paymentMethodCategory:e.paymentMethodCategory}):ye(s.error)?this.telemetryReporter?.terminal({outcome:"payment_declined",provider:r,paymentMethodCategory:e.paymentMethodCategory}):s.error?.type==="validation_error"?this.telemetryReporter?.terminal({outcome:"validation_rejected",provider:r,paymentMethodCategory:e.paymentMethodCategory}):s.status==="requires_action"?this.telemetryReporter?.terminal({outcome:"action_required",stage:"three_ds_handoff",provider:r,paymentMethodCategory:e.paymentMethodCategory}):s.status==="failed"&&s.error&&this.telemetryReporter?.error({errorCode:"PAYMENT_PROCESSING_FAILED",stage:"processing",provider:r,paymentMethodCategory:e.paymentMethodCategory}),s.status==="succeeded"||s.status==="failed"?this.telemetryReporter?.log({name:"payment.processing.completed",stage:"processing",provider:r,paymentMethodCategory:e.paymentMethodCategory}):this.telemetryReporter?.log({name:"operation.state_transition",stage:s.status==="requires_action"?"three_ds_handoff":"processing",provider:r,paymentMethodCategory:e.paymentMethodCategory}),s}catch(s){throw this.telemetryReporter?.performance({stage:"processing",durationMs:this.now()-t,durationMode:"machine",provider:r,paymentMethodCategory:e.paymentMethodCategory}),this.telemetryReporter?.error({errorCode:"PAYMENT_PROCESSING_FAILED",stage:"processing",provider:r,paymentMethodCategory:e.paymentMethodCategory}),s}}async retrieveSession(e,t){if(!e)throw new w.FloPayError("sessionId is required to retrieve a session.","validation_error",{param:"sessionId"});let r=await this.retrieveUnifiedSession(e,t);if(!r.data.session)throw new w.FloPayError("Session not found","api_error");return r.data.session}async retrieveUnifiedSession(e,t){if(!e)throw new w.FloPayError("sessionId is required.","validation_error",{param:"sessionId"});let r=(0,w.resolveBillingApiUrl)(t??this.config.billingApiUrl),s=this.now();this.telemetryReporter?.log({name:"session.read.started",stage:"session_read",requestCategory:"session_read"});let o,i=Re(r,{now:()=>this.telemetryReporter?.now()??_(),onFirstByte:a=>{o=a},onRetry:(a,l)=>{this.telemetryReporter?.log({name:"operation.retry",stage:a==="session_read"?"session_read":"processing",requestCategory:a,attempt:l})}});try{let a=await i.getUnifiedCheckoutSession(e);return o!==void 0&&(this.telemetryReporter?.log({name:"session.request.first_byte",stage:"session_first_byte",requestCategory:"session_read",statusClass:"2xx"}),this.telemetryReporter?.performance({stage:"session_first_byte",durationMs:o,durationMode:"machine",requestCategory:"session_read",statusClass:"2xx"})),this.telemetryReporter?.log({name:"session.request.completed",stage:"session_complete",requestCategory:"session_read",statusClass:"2xx"}),this.telemetryReporter?.log({name:"checkout.data.ready",stage:"checkout_data_ready"}),this.telemetryReporter?.performance({stage:"session_complete",durationMs:this.now()-s,durationMode:"machine",requestCategory:"session_read",statusClass:"2xx"}),a}catch(a){let l=a instanceof w.FloPayError?a.statusCode:void 0;throw this.telemetryReporter?.error({errorCode:a instanceof w.FloPayError&&a.code==="checkout_processing_timeout"?"REQUEST_TIMEOUT":"NETWORK_REQUEST_FAILED",stage:"session_read",provider:"flo",paymentMethodCategory:"unknown"}),this.telemetryReporter?.performance({stage:"session_complete",durationMs:this.now()-s,durationMode:"machine",requestCategory:"session_read",statusClass:l?`${Math.floor(l/100)}xx`:"network_error"}),a}}getRawProvider(){return this.provider.getRawProvider()}destroy(){this.telemetryReporter?.log({name:"checkout.unmount",stage:"unmount"}),this.telemetryReporter?.destroy(),this.currentElements?.destroy(),this.currentElements=null,this.provider.destroy()}};function Ve(n,e,t){let r=G;return new r(n,e,t)}var Ke=new Map;function he(n){return Array.isArray(n)?n.map(he):n&&typeof n=="object"?Object.fromEntries(Object.entries(n).filter(([,e])=>e!==void 0).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,he(t)])):n}function Et(n,e){return JSON.stringify([n,(0,R.resolveBillingApiUrl)(e?.billingApiUrl),e?.telemetry!==!1,e?.locale??"auto",e?.apiVersion??null,he(e?.appearance??null)])}var se=new Map;async function Be(n,e){if(!n){let a=new v({billingApiUrl:(0,R.resolveBillingApiUrl)(e?.billingApiUrl),sdkVersion:R.SDK_VERSION,enabled:e?.telemetry!==!1});throw a.error({errorCode:"CONFIGURATION_INVALID",stage:"sdk_initialize",paymentMethodCategory:"unknown"}),a.flush().catch(()=>{}).finally(()=>a.destroy()),new R.FloPayError("A publishable key is required to initialize FloPay.","validation_error",{param:"publishableKey"})}let t=Et(n,e),r=Ke.get(t);if(r)return e?.telemetry!==!1&&Ne(r)?.log({name:"sdk.cache.hit",stage:"sdk_initialize"}),r;let s=se.get(t);if(s)return s;let o={...e,publishableKey:n},i=(async()=>{let a=new v({billingApiUrl:(0,R.resolveBillingApiUrl)(o.billingApiUrl),sdkVersion:R.SDK_VERSION,enabled:o.telemetry!==!1}),l=a.now();a.log({name:"sdk.initialize.started",stage:"sdk_initialize"}),a.log({name:"sdk.cache.miss",stage:"sdk_initialize"}),a.log({name:"provider.load.started",stage:"provider_load",provider:"stripe"});let c=new K;try{await c.initialize(o)}catch(b){throw a.error({errorCode:"SDK_INITIALIZATION_FAILED",stage:"sdk_initialize",provider:"stripe",paymentMethodCategory:"unknown"}),a.destroy(),b}a.log({name:"provider.ready",stage:"provider_ready",provider:"stripe"}),a.log({name:"provider.availability.checked",stage:"provider_ready",provider:"stripe"}),a.log({name:"sdk.initialize.ready",stage:"sdk_initialize"});let u=a.now()-l;a.performance({stage:"sdk_initialize",durationMs:u,durationMode:"machine",provider:"stripe"}),a.performance({stage:"provider_ready",durationMs:u,durationMode:"machine",provider:"stripe"});let y=Ve(c,o,a);return Ke.set(t,y),y})();se.set(t,i);try{return await i}finally{se.get(t)===i&&se.delete(t)}}var m=require("@flopay/shared");var ge=5;function kt(n,e){let t=e?.error,r=I(e?.code)??I(t?.code)??`http_${n}`,s=I(e?.message)??I(t?.message)??Mt(r,n);return new m.FloPayError(s,"api_error",{code:r,statusCode:n})}function Mt(n,e){switch(n){case"CouponLimitExceeded":return`Too many coupon codes \u2014 a checkout session accepts at most ${ge}.`;case"CouponCurrencyUnsupported":return"One of the applied coupons has no price configured for the cart currency.";default:return`Failed to create checkout session (HTTP ${e}).`}}async function ze(n,e){let{billingApiUrl:t,checkoutBaseUrl:r,items:s=[],subscriptions:o=[],products:i,account:a,successUrl:l,cancelUrl:c,checkoutMode:u="confirm",couponCodes:y=[],tagsData:b,redirectParams:g={},setCookie:E=!0,timeoutMs:k=12e3,clientId:P,currency:Ce,utmMetadata:O,idempotencyKey:p}=n,z=(0,m.resolveIdempotencyKey)(p);if(y.length>ge)throw new m.FloPayError(`Too many coupon codes \u2014 a checkout session accepts at most ${ge}.`,"validation_error",{code:"CouponLimitExceeded",param:"couponCodes"});let q=i??(0,m.foldIntoProducts)(s,o),S=(0,m.resolveSessionCurrency)(Ce,s,o,q);if(!S)throw new m.FloPayError("currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.","validation_error",{code:"CurrencyRequired",param:"currency"});let j={clientId:P,checkoutVersion:m.SDK_VERSION,successUrl:l,cancelUrl:c,currency:S,checkoutMode:u,products:q.map(M=>(0,m.buildProductPayload)(M,S)),accountData:{userId:a.userId,firstName:a.firstName??null,lastName:a.lastName??null,email:a.email,country:a.country??null,gender:a.gender??null,city:a.city??null,state:a.state??null,zip:a.zip??null,addressLine1:a.addressLine1??null,addressLine2:a.addressLine2??null},couponCodes:y};b&&(j.tagsData=b),O?.length&&(j.utmMetadata=O);let ie=`${t.replace(/\/+$/,"")}/v1/checkouts/sessions`,ve=new AbortController,We=setTimeout(()=>ve.abort(),k),Te={"Content-Type":"application/json"};z&&(Te[m.IDEMPOTENCY_KEY_HEADER]=z);let U,W;try{let M=await fetch(ie,{method:"POST",headers:Te,body:JSON.stringify(j),signal:ve.signal});try{e?.(M.status)}catch{}U=M.status;try{W=await M.json()}catch{}}finally{clearTimeout(We)}if(U>=400)throw kt(U,W);if(U===201){let M=W?.data?.uuid,ae=W?.data?.nonce;if(!M)throw new Error("Checkout session created but no UUID was returned by the billing API");if(!ae)throw new m.FloPayError("Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.","api_error",{code:"MissingCheckoutSessionToken"});(q.length||S)&&x(M,{currency:S,products:q.map(f=>({code:f.code??f.providerItemId??f.providerPlanId,type:f.type,name:f.name??f.itemName??f.providerItemName??f.subscriptionName??f.providerPlanName??null,totalAmount:f.totalAmount,overrideAmount:f.overrideAmount,currency:f.currency??S}))});let Q=new URL(`${r.replace(/\/+$/,"")}/secure`);Q.searchParams.set("id",M);for(let[f,J]of Object.entries(g))Q.searchParams.set(f,J);if(E&&typeof window<"u"&&typeof document<"u"){let f=JSON.stringify({origin_url:c}),J=window.location.hostname.split(".").slice(-2).join(".");document.cookie=`checkout_data=${encodeURIComponent(f)}; domain=.${J}; path=/; max-age=3600; SameSite=Lax; Secure;`,document.cookie=`flopay_checkout_token=${encodeURIComponent(ae)}; domain=.${J}; path=/; max-age=3600; SameSite=Lax; Secure;`}return typeof window<"u"&&(window.location.href=Q.toString()),{status:201,redirectUrl:Q.toString(),nonce:ae}}return U===204?(typeof window<"u"&&(window.location.href=l),{status:204}):{status:U}}async function je(n){let e=$e(n),t=He(e);try{let r=await ze(n,t);return Ge(e,r),r}catch(r){throw fe(e.reporter,r),r}finally{Y(e.reporter)}}function He(n){let e=!1;return t=>{if(e)return;e=!0;let r=`${Math.floor(t/100)}xx`;n.reporter.log({name:"session.request.first_byte",stage:"session_first_byte",requestCategory:"session_create",statusClass:r}),n.reporter.performance({stage:"session_first_byte",durationMs:n.reporter.now()-n.startedAt,durationMode:"machine",requestCategory:"session_create",statusClass:r})}}function $e(n){let e=new v({billingApiUrl:n.billingApiUrl,sdkVersion:m.SDK_VERSION,enabled:n.telemetry!==!1}),t=e.now();return e.log({name:"session.create.started",stage:"session_create",requestCategory:"session_create"}),{reporter:e,startedAt:t}}function Ge(n,e){let t=`${Math.floor(e.status/100)}xx`;n.reporter.log({name:"session.request.completed",stage:"session_complete",requestCategory:"session_create",statusClass:t}),n.reporter.performance({stage:"session_complete",durationMs:n.reporter.now()-n.startedAt,durationMode:"machine",requestCategory:"session_create",statusClass:t})}function fe(n,e){if(e instanceof m.FloPayError&&e.type==="validation_error"){n.terminal({outcome:"validation_rejected",stage:"session_create",requestCategory:"session_create"});return}let t=e instanceof m.FloPayError?e.statusCode:void 0;n.error({errorCode:e instanceof Error&&e.name==="AbortError"?"REQUEST_TIMEOUT":e instanceof TypeError?"NETWORK_REQUEST_FAILED":"CHECKOUT_SESSION_CREATE_FAILED",stage:"session_create",requestCategory:"session_create",statusClass:e instanceof Error&&e.name==="AbortError"?"timeout":t?`${Math.floor(t/100)}xx`:"network_error"})}function Y(n){n.flush().catch(()=>{}).finally(()=>n.destroy())}async function Ye(n){let{maxRetries:e=3,...t}=n,r=$e(n),s=He(r);if(!Number.isFinite(e)||!Number.isInteger(e)||e<=0)throw r.reporter.terminal({outcome:"validation_rejected",stage:"session_create",requestCategory:"session_create"}),Y(r.reporter),new Error("Number of retries must be greater than 0");let o={...t,idempotencyKey:(0,m.resolveIdempotencyKey)(t.idempotencyKey)},i;for(let a=0;a<=e;a++)try{let l=await ze(o,s);return Ge(r,l),Y(r.reporter),l}catch(l){i=l;let c=l instanceof Error&&l.name==="AbortError",u=l instanceof m.FloPayError&&l.code===m.IDEMPOTENCY_IN_PROGRESS_CODE;if((c||u)&&a<e){r.reporter.log({name:"operation.retry",stage:"session_create",requestCategory:"session_create",attempt:a+1}),await new Promise(y=>setTimeout(y,100*Math.pow(2,a)));continue}throw fe(r.reporter,l),Y(r.reporter),l}throw fe(r.reporter,i),Y(r.reporter),i??new Error("Unknown error during checkout session creation")}0&&(module.exports={FloPay,FloPayElements,PaymentAPI,PciVaultCardCapture,StripeAdapter,cacheSessionDisplayData,clearSessionDisplayData,createCheckoutSession,createCheckoutSessionWithRetries,getSessionDisplayData,loadFloPay});
|