@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/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/catalog") {
162
+ if (path === "/v1/checkout/session") {
127
163
  return {
128
- ...demoEvent,
129
- shows: demoEvent.shows.map((show) => ({
130
- ...show,
131
- showOptions: [
132
- ...show.showOptions.filter(
133
- (o) => o.catalogVisibility !== "PROMO_GATED"
134
- ),
135
- ...unlocked.filter(
136
- (u) => show.showOptions.some((o) => o.id === u.id)
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 Object.assign(new Error("OTP required"), {
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
- return {
202
- id: `pay_${Date.now()}`,
203
- paymentStatus: "PENDING",
204
- paymentMethod: init.body?.paymentMethod,
205
- paymentInstructions: {
206
- alias: "tickean.demo",
207
- cvu: "0000003100010000000001",
208
- amount: init.body?.amount
209
- },
210
- redirectUrl: void 0,
211
- returnUrl: null
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(`Demo transport: unhandled ${init.method || "GET"} ${path}`);
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/index.ts
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
  });