@tickean/checkout-js 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/index.d.mts +281 -3
- package/dist/index.d.ts +281 -3
- package/dist/index.js +726 -47
- package/dist/index.mjs +719 -47
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21,10 +21,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
TickeanError: () => TickeanError,
|
|
24
|
+
checkoutReducer: () => checkoutReducer,
|
|
25
|
+
createCheckoutController: () => createCheckoutController,
|
|
26
|
+
createEventEmitterTelemetry: () => createEventEmitterTelemetry,
|
|
27
|
+
createInitialState: () => createInitialState,
|
|
28
|
+
createMemoryPersistence: () => createMemoryPersistence,
|
|
29
|
+
createNoopTelemetry: () => createNoopTelemetry,
|
|
30
|
+
createSessionStoragePersistence: () => createSessionStoragePersistence,
|
|
24
31
|
createTickean: () => createTickean
|
|
25
32
|
});
|
|
26
33
|
module.exports = __toCommonJS(index_exports);
|
|
27
34
|
|
|
35
|
+
// src/types.ts
|
|
36
|
+
var TickeanError = class extends Error {
|
|
37
|
+
constructor(payload, status, requestId) {
|
|
38
|
+
super(payload.message);
|
|
39
|
+
this.name = "TickeanError";
|
|
40
|
+
this.code = payload.code;
|
|
41
|
+
this.details = payload.details;
|
|
42
|
+
this.status = status;
|
|
43
|
+
this.requestId = requestId;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
28
47
|
// src/demo.ts
|
|
29
48
|
var demoEvent = {
|
|
30
49
|
id: "evt_demo",
|
|
@@ -98,10 +117,26 @@ function createDemoTransport() {
|
|
|
98
117
|
let unlocked = [];
|
|
99
118
|
let purchaseId = "purchase_demo";
|
|
100
119
|
let cartRef = "cart_demo";
|
|
120
|
+
let lastPayment = null;
|
|
121
|
+
let paymentConfirmed = false;
|
|
122
|
+
const publicEvent = () => ({
|
|
123
|
+
...demoEvent,
|
|
124
|
+
shows: demoEvent.shows.map((show) => ({
|
|
125
|
+
...show,
|
|
126
|
+
showOptions: [
|
|
127
|
+
...show.showOptions.filter((o) => o.catalogVisibility !== "PROMO_GATED"),
|
|
128
|
+
...unlocked.filter((u) => show.showOptions.some((o) => o.id === u.id))
|
|
129
|
+
]
|
|
130
|
+
}))
|
|
131
|
+
});
|
|
101
132
|
return {
|
|
102
133
|
async request(path, init) {
|
|
103
134
|
if (path === "/v1/checkout/sessions" && init.method === "POST") {
|
|
104
135
|
sessionToken = `demo_${Date.now()}`;
|
|
136
|
+
buyer = null;
|
|
137
|
+
unlocked = [];
|
|
138
|
+
paymentConfirmed = false;
|
|
139
|
+
lastPayment = null;
|
|
105
140
|
return {
|
|
106
141
|
sessionId: "sess_demo",
|
|
107
142
|
sessionToken,
|
|
@@ -120,25 +155,34 @@ function createDemoTransport() {
|
|
|
120
155
|
discounts: true,
|
|
121
156
|
transfer: true,
|
|
122
157
|
onlinePayments: true
|
|
123
|
-
}
|
|
158
|
+
},
|
|
159
|
+
phase: "browsing"
|
|
124
160
|
};
|
|
125
161
|
}
|
|
126
|
-
if (path === "/v1/checkout/
|
|
162
|
+
if (path === "/v1/checkout/session") {
|
|
127
163
|
return {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
164
|
+
sessionId: "sess_demo",
|
|
165
|
+
sessionToken,
|
|
166
|
+
expiresAt: new Date(Date.now() + 36e5).toISOString(),
|
|
167
|
+
event: publicEvent(),
|
|
168
|
+
capabilities: {
|
|
169
|
+
tickets: true,
|
|
170
|
+
discounts: true,
|
|
171
|
+
transfer: true,
|
|
172
|
+
onlinePayments: true
|
|
173
|
+
},
|
|
174
|
+
status: "ACTIVE",
|
|
175
|
+
otpVerified: Boolean(buyer),
|
|
176
|
+
buyerId: buyer?.id || null,
|
|
177
|
+
purchaseId: purchaseId.startsWith("purchase_") ? purchaseId : null,
|
|
178
|
+
shoppingCartReference: cartRef,
|
|
179
|
+
nextAction: lastPayment?.nextAction || { type: "none" },
|
|
180
|
+
phase: paymentConfirmed ? "completed" : lastPayment ? "requires_action" : buyer ? "ready_to_purchase" : "browsing"
|
|
140
181
|
};
|
|
141
182
|
}
|
|
183
|
+
if (path === "/v1/checkout/catalog") {
|
|
184
|
+
return publicEvent();
|
|
185
|
+
}
|
|
142
186
|
if (path === "/v1/checkout/quote" && init.method === "POST") {
|
|
143
187
|
const body = init.body;
|
|
144
188
|
if (body.discountCode?.toUpperCase() === "DEMO2X1") {
|
|
@@ -172,8 +216,9 @@ function createDemoTransport() {
|
|
|
172
216
|
}
|
|
173
217
|
if (path === "/v1/checkout/purchases") {
|
|
174
218
|
if (!buyer) {
|
|
175
|
-
throw
|
|
176
|
-
code: "checkout_otp_required"
|
|
219
|
+
throw new TickeanError({
|
|
220
|
+
code: "checkout_otp_required",
|
|
221
|
+
message: "OTP required"
|
|
177
222
|
});
|
|
178
223
|
}
|
|
179
224
|
const body = init.body;
|
|
@@ -198,47 +243,81 @@ function createDemoTransport() {
|
|
|
198
243
|
};
|
|
199
244
|
}
|
|
200
245
|
if (path === "/v1/checkout/payments") {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
246
|
+
const body = init.body;
|
|
247
|
+
const method = String(body?.paymentMethod || "TRANSFER").toUpperCase();
|
|
248
|
+
if (method === "TRANSFER") {
|
|
249
|
+
lastPayment = {
|
|
250
|
+
id: `pay_${Date.now()}`,
|
|
251
|
+
paymentStatus: "PENDING",
|
|
252
|
+
paymentMethod: method,
|
|
253
|
+
paymentInstructions: {
|
|
254
|
+
alias: "tickean.demo",
|
|
255
|
+
cvu: "0000003100010000000001",
|
|
256
|
+
amount: body?.amount
|
|
257
|
+
},
|
|
258
|
+
redirectUrl: void 0,
|
|
259
|
+
returnUrl: null,
|
|
260
|
+
nextAction: {
|
|
261
|
+
type: "display_instructions",
|
|
262
|
+
paymentInstructions: {
|
|
263
|
+
alias: "tickean.demo",
|
|
264
|
+
cvu: "0000003100010000000001",
|
|
265
|
+
amount: body?.amount
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
requiresAction: false
|
|
269
|
+
};
|
|
270
|
+
} else {
|
|
271
|
+
lastPayment = {
|
|
272
|
+
id: `pay_${Date.now()}`,
|
|
273
|
+
paymentStatus: "PENDING",
|
|
274
|
+
paymentMethod: method,
|
|
275
|
+
redirectUrl: "https://example.com/pay/demo",
|
|
276
|
+
returnUrl: null,
|
|
277
|
+
nextAction: {
|
|
278
|
+
type: "redirect",
|
|
279
|
+
url: "https://example.com/pay/demo"
|
|
280
|
+
},
|
|
281
|
+
requiresAction: true
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
return lastPayment;
|
|
285
|
+
}
|
|
286
|
+
if (path === "/v1/checkout/payments/confirm" && init.method === "POST") {
|
|
287
|
+
paymentConfirmed = true;
|
|
288
|
+
lastPayment = {
|
|
289
|
+
...lastPayment || { id: `pay_${Date.now()}` },
|
|
290
|
+
paymentStatus: "COMPLETED",
|
|
291
|
+
nextAction: { type: "none" },
|
|
292
|
+
requiresAction: false
|
|
212
293
|
};
|
|
294
|
+
return lastPayment;
|
|
213
295
|
}
|
|
214
296
|
if (path === "/v1/checkout/payments/status") {
|
|
215
297
|
return {
|
|
216
|
-
status: "PENDING",
|
|
298
|
+
status: paymentConfirmed ? "COMPLETED" : "PENDING",
|
|
299
|
+
requiresAction: Boolean(
|
|
300
|
+
lastPayment?.nextAction && lastPayment.nextAction.type !== "none" && !paymentConfirmed
|
|
301
|
+
),
|
|
302
|
+
phase: paymentConfirmed ? "completed" : lastPayment ? "requires_action" : "browsing",
|
|
303
|
+
nextAction: lastPayment?.nextAction || { type: "none" },
|
|
304
|
+
payment: lastPayment,
|
|
217
305
|
purchase: {
|
|
218
306
|
id: purchaseId,
|
|
219
|
-
status: "PENDING",
|
|
307
|
+
status: paymentConfirmed ? "COMPLETED" : "PENDING",
|
|
220
308
|
totalPrice: 0,
|
|
221
309
|
currency: "ARS",
|
|
222
310
|
shoppingCartReference: cartRef
|
|
223
311
|
}
|
|
224
312
|
};
|
|
225
313
|
}
|
|
226
|
-
throw new Error(
|
|
314
|
+
throw new Error(
|
|
315
|
+
`Demo transport: unhandled ${init.method || "GET"} ${path}`
|
|
316
|
+
);
|
|
227
317
|
}
|
|
228
318
|
};
|
|
229
319
|
}
|
|
230
320
|
|
|
231
|
-
// src/types.ts
|
|
232
|
-
var TickeanError = class extends Error {
|
|
233
|
-
constructor(payload, status) {
|
|
234
|
-
super(payload.message);
|
|
235
|
-
this.name = "TickeanError";
|
|
236
|
-
this.code = payload.code;
|
|
237
|
-
this.details = payload.details;
|
|
238
|
-
this.status = status;
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
|
|
242
321
|
// src/http.ts
|
|
243
322
|
function createHttpTransport(options) {
|
|
244
323
|
const fetchImpl = options.fetchImpl || fetch;
|
|
@@ -251,6 +330,9 @@ function createHttpTransport(options) {
|
|
|
251
330
|
if (init.sessionToken) {
|
|
252
331
|
headers["X-Tickean-Checkout-Session"] = init.sessionToken;
|
|
253
332
|
}
|
|
333
|
+
if (init.idempotencyKey) {
|
|
334
|
+
headers["Idempotency-Key"] = init.idempotencyKey;
|
|
335
|
+
}
|
|
254
336
|
const response = await fetchImpl(
|
|
255
337
|
`${options.apiBaseUrl.replace(/\/$/, "")}${path}`,
|
|
256
338
|
{
|
|
@@ -260,6 +342,7 @@ function createHttpTransport(options) {
|
|
|
260
342
|
credentials: "omit"
|
|
261
343
|
}
|
|
262
344
|
);
|
|
345
|
+
const requestId = response.headers.get("X-Request-Id") || response.headers.get("x-request-id") || void 0;
|
|
263
346
|
const json = await response.json().catch(() => ({}));
|
|
264
347
|
if (!response.ok) {
|
|
265
348
|
const err = json?.error || {};
|
|
@@ -269,7 +352,8 @@ function createHttpTransport(options) {
|
|
|
269
352
|
message: err.message || response.statusText || "Request failed",
|
|
270
353
|
details: err.details
|
|
271
354
|
},
|
|
272
|
-
response.status
|
|
355
|
+
response.status,
|
|
356
|
+
requestId
|
|
273
357
|
);
|
|
274
358
|
}
|
|
275
359
|
return json;
|
|
@@ -277,7 +361,7 @@ function createHttpTransport(options) {
|
|
|
277
361
|
};
|
|
278
362
|
}
|
|
279
363
|
|
|
280
|
-
// src/
|
|
364
|
+
// src/client.ts
|
|
281
365
|
function createTickean(options) {
|
|
282
366
|
if (!options.publishableKey && !options.demo) {
|
|
283
367
|
throw new TickeanError({
|
|
@@ -304,6 +388,9 @@ function createTickean(options) {
|
|
|
304
388
|
get session() {
|
|
305
389
|
return session;
|
|
306
390
|
},
|
|
391
|
+
set session(value) {
|
|
392
|
+
session = value;
|
|
393
|
+
},
|
|
307
394
|
async createSession(params) {
|
|
308
395
|
session = await transport.request("/v1/checkout/sessions", {
|
|
309
396
|
method: "POST",
|
|
@@ -311,6 +398,22 @@ function createTickean(options) {
|
|
|
311
398
|
});
|
|
312
399
|
return session;
|
|
313
400
|
},
|
|
401
|
+
async getSession() {
|
|
402
|
+
const active = requireSession();
|
|
403
|
+
const result = await transport.request(
|
|
404
|
+
"/v1/checkout/session",
|
|
405
|
+
{
|
|
406
|
+
sessionToken: active.sessionToken
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
session = {
|
|
410
|
+
...active,
|
|
411
|
+
...result,
|
|
412
|
+
sessionToken: active.sessionToken,
|
|
413
|
+
event: result.event || active.event
|
|
414
|
+
};
|
|
415
|
+
return session;
|
|
416
|
+
},
|
|
314
417
|
async getCatalog() {
|
|
315
418
|
const active = requireSession();
|
|
316
419
|
return transport.request("/v1/checkout/catalog", {
|
|
@@ -346,7 +449,8 @@ function createTickean(options) {
|
|
|
346
449
|
return transport.request("/v1/checkout/purchases", {
|
|
347
450
|
method: "POST",
|
|
348
451
|
body: params,
|
|
349
|
-
sessionToken: active.sessionToken
|
|
452
|
+
sessionToken: active.sessionToken,
|
|
453
|
+
idempotencyKey: params.idempotencyKey
|
|
350
454
|
});
|
|
351
455
|
},
|
|
352
456
|
async createPayment(params) {
|
|
@@ -354,7 +458,17 @@ function createTickean(options) {
|
|
|
354
458
|
return transport.request("/v1/checkout/payments", {
|
|
355
459
|
method: "POST",
|
|
356
460
|
body: params,
|
|
357
|
-
sessionToken: active.sessionToken
|
|
461
|
+
sessionToken: active.sessionToken,
|
|
462
|
+
idempotencyKey: params.idempotencyKey
|
|
463
|
+
});
|
|
464
|
+
},
|
|
465
|
+
async confirmPayment(params) {
|
|
466
|
+
const active = requireSession();
|
|
467
|
+
return transport.request("/v1/checkout/payments/confirm", {
|
|
468
|
+
method: "POST",
|
|
469
|
+
body: params || {},
|
|
470
|
+
sessionToken: active.sessionToken,
|
|
471
|
+
idempotencyKey: params?.idempotencyKey
|
|
358
472
|
});
|
|
359
473
|
},
|
|
360
474
|
async getPaymentStatus() {
|
|
@@ -375,13 +489,16 @@ function createTickean(options) {
|
|
|
375
489
|
});
|
|
376
490
|
}
|
|
377
491
|
const status = await this.getPaymentStatus();
|
|
378
|
-
if (["COMPLETED", "CONFIRMED", "PAID"].includes(
|
|
492
|
+
if (["COMPLETED", "CONFIRMED", "PAID", "SUCCESS"].includes(
|
|
379
493
|
String(status.status || "").toUpperCase()
|
|
380
|
-
) || ["COMPLETED", "CONFIRMED", "PAID"].includes(
|
|
494
|
+
) || ["COMPLETED", "CONFIRMED", "PAID", "SUCCESS"].includes(
|
|
381
495
|
String(status.purchase?.status || "").toUpperCase()
|
|
382
496
|
)) {
|
|
383
497
|
return status;
|
|
384
498
|
}
|
|
499
|
+
if (status.nextAction && status.nextAction.type !== "none" && status.requiresAction) {
|
|
500
|
+
return status;
|
|
501
|
+
}
|
|
385
502
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
386
503
|
}
|
|
387
504
|
throw new TickeanError({
|
|
@@ -391,8 +508,570 @@ function createTickean(options) {
|
|
|
391
508
|
}
|
|
392
509
|
};
|
|
393
510
|
}
|
|
511
|
+
|
|
512
|
+
// src/state.ts
|
|
513
|
+
function createInitialState(partial) {
|
|
514
|
+
return {
|
|
515
|
+
phase: "initializing",
|
|
516
|
+
session: null,
|
|
517
|
+
event: null,
|
|
518
|
+
cart: [],
|
|
519
|
+
discountCode: null,
|
|
520
|
+
quote: null,
|
|
521
|
+
isQuoting: false,
|
|
522
|
+
buyer: null,
|
|
523
|
+
buyerVerified: false,
|
|
524
|
+
otpSent: false,
|
|
525
|
+
purchase: null,
|
|
526
|
+
payment: null,
|
|
527
|
+
nextAction: { type: "none" },
|
|
528
|
+
error: null,
|
|
529
|
+
loading: true,
|
|
530
|
+
...partial
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function derivePhase(state) {
|
|
534
|
+
if (state.phase === "completed" || state.phase === "failed" || state.phase === "expired" || state.phase === "purchasing" || state.phase === "processing" || state.phase === "requires_action" || state.phase === "initializing") {
|
|
535
|
+
return state.phase;
|
|
536
|
+
}
|
|
537
|
+
if (state.isQuoting) return "quoting";
|
|
538
|
+
if (state.otpSent && !state.buyerVerified) return "verifying_buyer";
|
|
539
|
+
if (state.buyerVerified && state.cart.length > 0) return "ready_to_purchase";
|
|
540
|
+
return "browsing";
|
|
541
|
+
}
|
|
542
|
+
function checkoutReducer(state, action) {
|
|
543
|
+
switch (action.type) {
|
|
544
|
+
case "INIT_START":
|
|
545
|
+
return { ...state, loading: true, error: null, phase: "initializing" };
|
|
546
|
+
case "INIT_SUCCESS": {
|
|
547
|
+
const next = {
|
|
548
|
+
...state,
|
|
549
|
+
loading: false,
|
|
550
|
+
error: null,
|
|
551
|
+
session: action.session,
|
|
552
|
+
event: action.event,
|
|
553
|
+
phase: "browsing"
|
|
554
|
+
};
|
|
555
|
+
return { ...next, phase: derivePhase(next) };
|
|
556
|
+
}
|
|
557
|
+
case "INIT_FAILURE":
|
|
558
|
+
return {
|
|
559
|
+
...state,
|
|
560
|
+
loading: false,
|
|
561
|
+
error: action.error,
|
|
562
|
+
phase: "failed"
|
|
563
|
+
};
|
|
564
|
+
case "SET_CART": {
|
|
565
|
+
const next = { ...state, cart: action.cart, error: null };
|
|
566
|
+
return { ...next, phase: derivePhase(next) };
|
|
567
|
+
}
|
|
568
|
+
case "SET_DISCOUNT":
|
|
569
|
+
return { ...state, discountCode: action.discountCode };
|
|
570
|
+
case "QUOTE_START": {
|
|
571
|
+
const next = { ...state, isQuoting: true, error: null };
|
|
572
|
+
return { ...next, phase: "quoting" };
|
|
573
|
+
}
|
|
574
|
+
case "QUOTE_SUCCESS": {
|
|
575
|
+
const next = {
|
|
576
|
+
...state,
|
|
577
|
+
isQuoting: false,
|
|
578
|
+
quote: action.quote
|
|
579
|
+
};
|
|
580
|
+
return { ...next, phase: derivePhase(next) };
|
|
581
|
+
}
|
|
582
|
+
case "QUOTE_FAILURE":
|
|
583
|
+
return {
|
|
584
|
+
...state,
|
|
585
|
+
isQuoting: false,
|
|
586
|
+
error: action.error,
|
|
587
|
+
phase: derivePhase({ ...state, isQuoting: false })
|
|
588
|
+
};
|
|
589
|
+
case "OTP_SENT": {
|
|
590
|
+
const next = { ...state, otpSent: true, error: null };
|
|
591
|
+
return { ...next, phase: "verifying_buyer" };
|
|
592
|
+
}
|
|
593
|
+
case "OTP_VERIFIED": {
|
|
594
|
+
const next = {
|
|
595
|
+
...state,
|
|
596
|
+
buyer: action.buyer,
|
|
597
|
+
buyerVerified: true,
|
|
598
|
+
otpSent: true,
|
|
599
|
+
error: null
|
|
600
|
+
};
|
|
601
|
+
return { ...next, phase: derivePhase(next) };
|
|
602
|
+
}
|
|
603
|
+
case "SET_EVENT":
|
|
604
|
+
return { ...state, event: action.event };
|
|
605
|
+
case "PURCHASE_START":
|
|
606
|
+
return { ...state, phase: "purchasing", error: null };
|
|
607
|
+
case "PURCHASE_SUCCESS": {
|
|
608
|
+
const requiresProviderAction = action.nextAction.type !== "none" && action.nextAction.type !== "display_instructions";
|
|
609
|
+
return {
|
|
610
|
+
...state,
|
|
611
|
+
purchase: action.purchase,
|
|
612
|
+
payment: action.payment,
|
|
613
|
+
nextAction: action.nextAction,
|
|
614
|
+
phase: requiresProviderAction ? "requires_action" : "processing",
|
|
615
|
+
error: null
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
case "PURCHASE_FAILURE":
|
|
619
|
+
return { ...state, phase: "failed", error: action.error };
|
|
620
|
+
case "SET_NEXT_ACTION":
|
|
621
|
+
return {
|
|
622
|
+
...state,
|
|
623
|
+
nextAction: action.nextAction,
|
|
624
|
+
payment: action.payment === void 0 ? state.payment : action.payment,
|
|
625
|
+
phase: action.nextAction.type !== "none" && action.nextAction.type !== "display_instructions" ? "requires_action" : state.phase === "requires_action" ? "processing" : state.phase
|
|
626
|
+
};
|
|
627
|
+
case "PROCESSING":
|
|
628
|
+
return { ...state, phase: "processing" };
|
|
629
|
+
case "COMPLETED":
|
|
630
|
+
return {
|
|
631
|
+
...state,
|
|
632
|
+
phase: "completed",
|
|
633
|
+
purchase: action.purchase ?? state.purchase,
|
|
634
|
+
nextAction: { type: "none" },
|
|
635
|
+
error: null
|
|
636
|
+
};
|
|
637
|
+
case "FAILED":
|
|
638
|
+
return { ...state, phase: "failed", error: action.error };
|
|
639
|
+
case "EXPIRED":
|
|
640
|
+
return { ...state, phase: "expired" };
|
|
641
|
+
case "CLEAR_ERROR":
|
|
642
|
+
return { ...state, error: null };
|
|
643
|
+
case "REHYDRATE":
|
|
644
|
+
return { ...state, ...action.partial };
|
|
645
|
+
default:
|
|
646
|
+
return state;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/persistence.ts
|
|
651
|
+
function createMemoryPersistence() {
|
|
652
|
+
const store = /* @__PURE__ */ new Map();
|
|
653
|
+
return {
|
|
654
|
+
get(key) {
|
|
655
|
+
return store.get(key) ?? null;
|
|
656
|
+
},
|
|
657
|
+
set(key, value) {
|
|
658
|
+
store.set(key, { ...value });
|
|
659
|
+
},
|
|
660
|
+
remove(key) {
|
|
661
|
+
store.delete(key);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function createSessionStoragePersistence(storage) {
|
|
666
|
+
const getStorage = () => {
|
|
667
|
+
if (storage) return storage;
|
|
668
|
+
try {
|
|
669
|
+
if (typeof sessionStorage !== "undefined") return sessionStorage;
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
};
|
|
674
|
+
return {
|
|
675
|
+
get(key) {
|
|
676
|
+
const s = getStorage();
|
|
677
|
+
if (!s) return null;
|
|
678
|
+
try {
|
|
679
|
+
const raw = s.getItem(key);
|
|
680
|
+
if (!raw) return null;
|
|
681
|
+
return JSON.parse(raw);
|
|
682
|
+
} catch {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
set(key, value) {
|
|
687
|
+
const s = getStorage();
|
|
688
|
+
if (!s) return;
|
|
689
|
+
try {
|
|
690
|
+
const safe = {
|
|
691
|
+
sessionToken: value.sessionToken,
|
|
692
|
+
eventSlug: value.eventSlug,
|
|
693
|
+
cart: value.cart,
|
|
694
|
+
discountCode: value.discountCode,
|
|
695
|
+
phase: value.phase,
|
|
696
|
+
buyerVerified: value.buyerVerified,
|
|
697
|
+
purchaseId: value.purchaseId
|
|
698
|
+
};
|
|
699
|
+
s.setItem(key, JSON.stringify(safe));
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
remove(key) {
|
|
704
|
+
const s = getStorage();
|
|
705
|
+
if (!s) return;
|
|
706
|
+
try {
|
|
707
|
+
s.removeItem(key);
|
|
708
|
+
} catch {
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/telemetry.ts
|
|
715
|
+
function createNoopTelemetry() {
|
|
716
|
+
return {
|
|
717
|
+
track() {
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function createEventEmitterTelemetry(listeners = []) {
|
|
722
|
+
const subs = new Set(listeners);
|
|
723
|
+
return {
|
|
724
|
+
track(event) {
|
|
725
|
+
for (const listener of subs) {
|
|
726
|
+
try {
|
|
727
|
+
listener(event);
|
|
728
|
+
} catch {
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
subscribe(listener) {
|
|
733
|
+
subs.add(listener);
|
|
734
|
+
return () => {
|
|
735
|
+
subs.delete(listener);
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/controller.ts
|
|
742
|
+
function generateIdempotencyKey() {
|
|
743
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
744
|
+
return crypto.randomUUID();
|
|
745
|
+
}
|
|
746
|
+
return `idem_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
747
|
+
}
|
|
748
|
+
function toTickeanError(err) {
|
|
749
|
+
if (err instanceof TickeanError) return err;
|
|
750
|
+
const anyErr = err;
|
|
751
|
+
return new TickeanError(
|
|
752
|
+
{
|
|
753
|
+
code: anyErr?.code || "checkout_unknown_error",
|
|
754
|
+
message: anyErr?.message || "Unexpected checkout error",
|
|
755
|
+
details: anyErr?.details
|
|
756
|
+
},
|
|
757
|
+
anyErr?.status
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
function createCheckoutController(options) {
|
|
761
|
+
const client = createTickean(options);
|
|
762
|
+
const telemetry = options.telemetry || createNoopTelemetry();
|
|
763
|
+
const persistenceKey = options.persistenceKey || `tickean.checkout.${options.eventSlug}.${options.publishableKey || "demo"}`;
|
|
764
|
+
const persistence = options.persistence === false ? null : options.persistence || (typeof sessionStorage !== "undefined" ? createSessionStoragePersistence() : createMemoryPersistence());
|
|
765
|
+
let state = createInitialState();
|
|
766
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
767
|
+
let quoteTimer = null;
|
|
768
|
+
let quoteGeneration = 0;
|
|
769
|
+
const quoteDebounceMs = options.quoteDebounceMs ?? 300;
|
|
770
|
+
let disposed = false;
|
|
771
|
+
const emit = (name, properties) => {
|
|
772
|
+
telemetry.track({ name, timestamp: Date.now(), properties });
|
|
773
|
+
};
|
|
774
|
+
const notify = () => {
|
|
775
|
+
const snapshot = getSnapshot();
|
|
776
|
+
for (const listener of listeners) {
|
|
777
|
+
try {
|
|
778
|
+
listener(snapshot);
|
|
779
|
+
} catch {
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
const dispatch = (action) => {
|
|
784
|
+
state = checkoutReducer(state, action);
|
|
785
|
+
persist();
|
|
786
|
+
notify();
|
|
787
|
+
};
|
|
788
|
+
const persist = () => {
|
|
789
|
+
if (!persistence) return;
|
|
790
|
+
persistence.set(persistenceKey, {
|
|
791
|
+
sessionToken: state.session?.sessionToken,
|
|
792
|
+
eventSlug: options.eventSlug,
|
|
793
|
+
cart: state.cart,
|
|
794
|
+
discountCode: state.discountCode,
|
|
795
|
+
phase: state.phase,
|
|
796
|
+
buyerVerified: state.buyerVerified,
|
|
797
|
+
purchaseId: state.purchase?.id ?? null
|
|
798
|
+
});
|
|
799
|
+
};
|
|
800
|
+
const getSnapshot = () => state;
|
|
801
|
+
const subscribe = (listener) => {
|
|
802
|
+
listeners.add(listener);
|
|
803
|
+
return () => {
|
|
804
|
+
listeners.delete(listener);
|
|
805
|
+
};
|
|
806
|
+
};
|
|
807
|
+
const scheduleQuote = () => {
|
|
808
|
+
if (quoteTimer) clearTimeout(quoteTimer);
|
|
809
|
+
if (!state.session || state.cart.length === 0) {
|
|
810
|
+
dispatch({
|
|
811
|
+
type: "QUOTE_SUCCESS",
|
|
812
|
+
quote: {
|
|
813
|
+
valid: true,
|
|
814
|
+
totalPrice: 0,
|
|
815
|
+
pricingBreakdown: { subtotal: 0 }
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const gen = ++quoteGeneration;
|
|
821
|
+
quoteTimer = setTimeout(async () => {
|
|
822
|
+
dispatch({ type: "QUOTE_START" });
|
|
823
|
+
emit("checkout.quote.start", { items: state.cart.length });
|
|
824
|
+
try {
|
|
825
|
+
const quote = await client.quote({
|
|
826
|
+
items: state.cart,
|
|
827
|
+
discountCode: state.discountCode || void 0
|
|
828
|
+
});
|
|
829
|
+
if (disposed || gen !== quoteGeneration) return;
|
|
830
|
+
dispatch({ type: "QUOTE_SUCCESS", quote });
|
|
831
|
+
emit("checkout.quote.success", { totalPrice: quote.totalPrice });
|
|
832
|
+
if ((quote.unlockedShowOptions || []).length > 0) {
|
|
833
|
+
const catalog = await client.getCatalog();
|
|
834
|
+
if (!disposed && gen === quoteGeneration) {
|
|
835
|
+
dispatch({ type: "SET_EVENT", event: catalog });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
} catch (err) {
|
|
839
|
+
if (disposed || gen !== quoteGeneration) return;
|
|
840
|
+
const error = toTickeanError(err);
|
|
841
|
+
dispatch({ type: "QUOTE_FAILURE", error });
|
|
842
|
+
emit("checkout.quote.error", { code: error.code });
|
|
843
|
+
}
|
|
844
|
+
}, quoteDebounceMs);
|
|
845
|
+
};
|
|
846
|
+
const setCartItem = (showOptionId, amount) => {
|
|
847
|
+
const next = state.cart.filter((item) => item.showOptionId !== showOptionId);
|
|
848
|
+
if (amount > 0) next.push({ showOptionId, amount });
|
|
849
|
+
dispatch({ type: "SET_CART", cart: next });
|
|
850
|
+
emit("checkout.cart.change", { showOptionId, amount });
|
|
851
|
+
scheduleQuote();
|
|
852
|
+
};
|
|
853
|
+
const setCart = (cart) => {
|
|
854
|
+
dispatch({
|
|
855
|
+
type: "SET_CART",
|
|
856
|
+
cart: cart.filter((item) => item.amount > 0)
|
|
857
|
+
});
|
|
858
|
+
emit("checkout.cart.change", { count: cart.length });
|
|
859
|
+
scheduleQuote();
|
|
860
|
+
};
|
|
861
|
+
const applyDiscountCode = async (code) => {
|
|
862
|
+
dispatch({ type: "SET_DISCOUNT", discountCode: code || null });
|
|
863
|
+
if (!state.session) {
|
|
864
|
+
throw new TickeanError({
|
|
865
|
+
code: "checkout_session_required",
|
|
866
|
+
message: "Session required"
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
dispatch({ type: "QUOTE_START" });
|
|
870
|
+
try {
|
|
871
|
+
const quote = await client.quote({
|
|
872
|
+
items: state.cart,
|
|
873
|
+
discountCode: code || void 0
|
|
874
|
+
});
|
|
875
|
+
dispatch({ type: "QUOTE_SUCCESS", quote });
|
|
876
|
+
emit("checkout.discount.applied", { code: code || null });
|
|
877
|
+
if ((quote.unlockedShowOptions || []).length > 0) {
|
|
878
|
+
const catalog = await client.getCatalog();
|
|
879
|
+
dispatch({ type: "SET_EVENT", event: catalog });
|
|
880
|
+
}
|
|
881
|
+
return quote;
|
|
882
|
+
} catch (err) {
|
|
883
|
+
const error = toTickeanError(err);
|
|
884
|
+
dispatch({ type: "QUOTE_FAILURE", error });
|
|
885
|
+
throw error;
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
const sendOtp = async (phone, channel) => {
|
|
889
|
+
await client.sendOtp({ phone, channel });
|
|
890
|
+
dispatch({ type: "OTP_SENT" });
|
|
891
|
+
emit("checkout.otp.sent");
|
|
892
|
+
};
|
|
893
|
+
const verifyOtp = async (params) => {
|
|
894
|
+
const result = await client.verifyOtp(params);
|
|
895
|
+
const buyer = result.buyer || {
|
|
896
|
+
id: "buyer",
|
|
897
|
+
phone: params.phone,
|
|
898
|
+
name: params.name,
|
|
899
|
+
email: params.email
|
|
900
|
+
};
|
|
901
|
+
dispatch({ type: "OTP_VERIFIED", buyer });
|
|
902
|
+
emit("checkout.otp.verified");
|
|
903
|
+
};
|
|
904
|
+
const purchaseAndPay = async (params) => {
|
|
905
|
+
dispatch({ type: "PURCHASE_START" });
|
|
906
|
+
const idempotencyKey = params.idempotencyKey || generateIdempotencyKey();
|
|
907
|
+
emit("checkout.purchase.start", { paymentMethod: params.paymentMethod });
|
|
908
|
+
try {
|
|
909
|
+
const purchase = await client.createPurchase({
|
|
910
|
+
items: state.cart,
|
|
911
|
+
paymentMethod: params.paymentMethod,
|
|
912
|
+
currency: params.currency,
|
|
913
|
+
discountCode: params.discountCode ?? state.discountCode ?? void 0,
|
|
914
|
+
expectedTotal: state.quote?.totalPrice,
|
|
915
|
+
showId: params.showId,
|
|
916
|
+
idempotencyKey
|
|
917
|
+
});
|
|
918
|
+
const payment = await client.createPayment({
|
|
919
|
+
orderId: purchase.purchase.id,
|
|
920
|
+
paymentMethod: params.paymentMethod,
|
|
921
|
+
currency: params.currency,
|
|
922
|
+
amount: purchase.purchase.totalPrice,
|
|
923
|
+
idempotencyKey: `${idempotencyKey}:payment`
|
|
924
|
+
});
|
|
925
|
+
const nextAction = payment.nextAction || (payment.paymentInstructions ? {
|
|
926
|
+
type: "display_instructions",
|
|
927
|
+
paymentInstructions: payment.paymentInstructions
|
|
928
|
+
} : payment.redirectUrl ? { type: "redirect", url: payment.redirectUrl } : { type: "none" });
|
|
929
|
+
dispatch({
|
|
930
|
+
type: "PURCHASE_SUCCESS",
|
|
931
|
+
purchase: purchase.purchase,
|
|
932
|
+
payment,
|
|
933
|
+
nextAction
|
|
934
|
+
});
|
|
935
|
+
emit("checkout.purchase.success", {
|
|
936
|
+
purchaseId: purchase.purchase.id,
|
|
937
|
+
nextActionType: nextAction.type
|
|
938
|
+
});
|
|
939
|
+
return { purchaseId: purchase.purchase.id, payment, nextAction };
|
|
940
|
+
} catch (err) {
|
|
941
|
+
const error = toTickeanError(err);
|
|
942
|
+
dispatch({ type: "PURCHASE_FAILURE", error });
|
|
943
|
+
emit("checkout.purchase.error", { code: error.code });
|
|
944
|
+
throw error;
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
const confirmPayment = async (params) => {
|
|
948
|
+
const payment = await client.confirmPayment({
|
|
949
|
+
...params,
|
|
950
|
+
idempotencyKey: params?.idempotencyKey || generateIdempotencyKey()
|
|
951
|
+
});
|
|
952
|
+
const nextAction = payment.nextAction || { type: "none" };
|
|
953
|
+
dispatch({ type: "SET_NEXT_ACTION", nextAction, payment });
|
|
954
|
+
emit("checkout.payment.confirmed", { nextActionType: nextAction.type });
|
|
955
|
+
return payment;
|
|
956
|
+
};
|
|
957
|
+
const watchPayment = async (watchOptions) => {
|
|
958
|
+
dispatch({ type: "PROCESSING" });
|
|
959
|
+
emit("checkout.payment.watch.start");
|
|
960
|
+
try {
|
|
961
|
+
const status = await client.watchPayment(watchOptions);
|
|
962
|
+
if (["COMPLETED", "CONFIRMED", "PAID", "SUCCESS"].includes(
|
|
963
|
+
String(status.status || "").toUpperCase()
|
|
964
|
+
)) {
|
|
965
|
+
dispatch({ type: "COMPLETED", purchase: status.purchase });
|
|
966
|
+
emit("checkout.completed");
|
|
967
|
+
} else if (status.nextAction && status.nextAction.type !== "none") {
|
|
968
|
+
dispatch({
|
|
969
|
+
type: "SET_NEXT_ACTION",
|
|
970
|
+
nextAction: status.nextAction,
|
|
971
|
+
payment: status.payment
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
return status;
|
|
975
|
+
} catch (err) {
|
|
976
|
+
const error = toTickeanError(err);
|
|
977
|
+
if (error.code !== "checkout_payment_timeout") {
|
|
978
|
+
dispatch({ type: "FAILED", error });
|
|
979
|
+
}
|
|
980
|
+
emit("checkout.payment.watch.error", { code: error.code });
|
|
981
|
+
throw error;
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
const refreshCatalog = async () => {
|
|
985
|
+
const catalog = await client.getCatalog();
|
|
986
|
+
dispatch({ type: "SET_EVENT", event: catalog });
|
|
987
|
+
return catalog;
|
|
988
|
+
};
|
|
989
|
+
const initialize = async () => {
|
|
990
|
+
dispatch({ type: "INIT_START" });
|
|
991
|
+
emit("checkout.init.start", { eventSlug: options.eventSlug });
|
|
992
|
+
try {
|
|
993
|
+
const stored = persistence?.get(persistenceKey);
|
|
994
|
+
let session = await client.createSession({
|
|
995
|
+
eventSlug: options.eventSlug,
|
|
996
|
+
returnUrl: options.returnUrl
|
|
997
|
+
});
|
|
998
|
+
if (stored?.sessionToken && stored.sessionToken !== session.sessionToken) {
|
|
999
|
+
try {
|
|
1000
|
+
client.session = {
|
|
1001
|
+
...session,
|
|
1002
|
+
sessionToken: stored.sessionToken
|
|
1003
|
+
};
|
|
1004
|
+
const resumed = await client.getSession();
|
|
1005
|
+
session = {
|
|
1006
|
+
...session,
|
|
1007
|
+
...resumed,
|
|
1008
|
+
sessionToken: stored.sessionToken,
|
|
1009
|
+
event: resumed.event || session.event
|
|
1010
|
+
};
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
dispatch({
|
|
1015
|
+
type: "INIT_SUCCESS",
|
|
1016
|
+
session,
|
|
1017
|
+
event: session.event
|
|
1018
|
+
});
|
|
1019
|
+
if (stored?.cart?.length) {
|
|
1020
|
+
dispatch({ type: "SET_CART", cart: stored.cart });
|
|
1021
|
+
if (stored.discountCode) {
|
|
1022
|
+
dispatch({ type: "SET_DISCOUNT", discountCode: stored.discountCode });
|
|
1023
|
+
}
|
|
1024
|
+
if (stored.buyerVerified) {
|
|
1025
|
+
dispatch({
|
|
1026
|
+
type: "REHYDRATE",
|
|
1027
|
+
partial: { buyerVerified: true }
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
scheduleQuote();
|
|
1031
|
+
}
|
|
1032
|
+
emit("checkout.init.success", { sessionId: session.sessionId });
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
const error = toTickeanError(err);
|
|
1035
|
+
dispatch({ type: "INIT_FAILURE", error });
|
|
1036
|
+
emit("checkout.init.error", { code: error.code });
|
|
1037
|
+
throw error;
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
const dispose = () => {
|
|
1041
|
+
disposed = true;
|
|
1042
|
+
if (quoteTimer) clearTimeout(quoteTimer);
|
|
1043
|
+
listeners.clear();
|
|
1044
|
+
};
|
|
1045
|
+
const ready = initialize().catch(() => {
|
|
1046
|
+
});
|
|
1047
|
+
return {
|
|
1048
|
+
client,
|
|
1049
|
+
ready,
|
|
1050
|
+
getSnapshot,
|
|
1051
|
+
subscribe,
|
|
1052
|
+
setCartItem,
|
|
1053
|
+
setCart,
|
|
1054
|
+
applyDiscountCode,
|
|
1055
|
+
sendOtp,
|
|
1056
|
+
verifyOtp,
|
|
1057
|
+
purchaseAndPay,
|
|
1058
|
+
confirmPayment,
|
|
1059
|
+
watchPayment,
|
|
1060
|
+
refreshCatalog,
|
|
1061
|
+
dispose,
|
|
1062
|
+
/** @internal test helper */
|
|
1063
|
+
_dispatch: dispatch
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
394
1066
|
// Annotate the CommonJS export names for ESM import in node:
|
|
395
1067
|
0 && (module.exports = {
|
|
396
1068
|
TickeanError,
|
|
1069
|
+
checkoutReducer,
|
|
1070
|
+
createCheckoutController,
|
|
1071
|
+
createEventEmitterTelemetry,
|
|
1072
|
+
createInitialState,
|
|
1073
|
+
createMemoryPersistence,
|
|
1074
|
+
createNoopTelemetry,
|
|
1075
|
+
createSessionStoragePersistence,
|
|
397
1076
|
createTickean
|
|
398
1077
|
});
|