@tickean/checkout-js 0.1.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 ADDED
@@ -0,0 +1,5 @@
1
+ # @tickean/checkout-js
2
+
3
+ Framework-agnostic TypeScript client for Tickean Headless Checkout.
4
+
5
+ See repository root README and `docs/readme/` for guides.
@@ -0,0 +1,168 @@
1
+ type TickeanErrorPayload = {
2
+ code: string;
3
+ message: string;
4
+ details?: Record<string, unknown>;
5
+ };
6
+ declare class TickeanError extends Error {
7
+ readonly code: string;
8
+ readonly details?: Record<string, unknown>;
9
+ readonly status?: number;
10
+ constructor(payload: TickeanErrorPayload, status?: number);
11
+ }
12
+ type CreateTickeanOptions = {
13
+ publishableKey: string;
14
+ apiBaseUrl?: string;
15
+ /** When true, uses an in-memory mock adapter (no network). */
16
+ demo?: boolean;
17
+ fetchImpl?: typeof fetch;
18
+ };
19
+ type CheckoutSession = {
20
+ sessionId: string;
21
+ sessionToken: string;
22
+ expiresAt: string;
23
+ event: PublicEvent;
24
+ capabilities: Record<string, boolean>;
25
+ };
26
+ type PublicShowOption = {
27
+ id: string;
28
+ name?: string;
29
+ description?: string;
30
+ price: number;
31
+ currency?: string;
32
+ stock?: number;
33
+ maxPerPurchase?: number;
34
+ optionType?: string;
35
+ accessScope?: string;
36
+ coveredShowIds?: string[];
37
+ passIssuanceMode?: "PER_DAY" | "SINGLE_PASS";
38
+ catalogVisibility?: "PUBLIC" | "PROMO_GATED";
39
+ promotionNxM?: unknown;
40
+ quantityDiscount?: unknown;
41
+ status?: string;
42
+ };
43
+ type PublicShow = {
44
+ id: string;
45
+ title?: string;
46
+ date?: string;
47
+ endDate?: string;
48
+ showOptions: PublicShowOption[];
49
+ };
50
+ type PublicEvent = {
51
+ id: string;
52
+ slug: string;
53
+ title: string;
54
+ description?: string;
55
+ images?: unknown[];
56
+ location?: unknown;
57
+ availablePaymentMethods?: string[];
58
+ organization?: {
59
+ id?: string;
60
+ slug?: string;
61
+ name?: string;
62
+ logo?: string | null;
63
+ } | null;
64
+ shows: PublicShow[];
65
+ };
66
+ type CartItem = {
67
+ showOptionId: string;
68
+ amount: number;
69
+ };
70
+ type QuoteResult = {
71
+ valid: boolean;
72
+ totalPrice: number;
73
+ pricingBreakdown?: unknown;
74
+ discountCode?: unknown;
75
+ unlockedShowOptionIds?: string[];
76
+ unlockedShowOptions?: PublicShowOption[];
77
+ };
78
+ type Buyer = {
79
+ id: string;
80
+ phone: string;
81
+ name?: string;
82
+ email?: string;
83
+ };
84
+ type PurchaseResult = {
85
+ purchase: {
86
+ id: string;
87
+ status: string;
88
+ totalPrice: number;
89
+ currency: string;
90
+ shoppingCartReference: string;
91
+ paymentMethod?: string;
92
+ };
93
+ shoppingCartReference: string;
94
+ cartSessionToken: string;
95
+ };
96
+ type PaymentResult = {
97
+ id: string;
98
+ paymentStatus?: string;
99
+ paymentMethod?: string;
100
+ paymentInstructions?: unknown;
101
+ redirectUrl?: string;
102
+ initPoint?: string;
103
+ returnUrl?: string | null;
104
+ stripe?: unknown;
105
+ fintocWidget?: unknown;
106
+ dlocalGo?: unknown;
107
+ airwallex?: unknown;
108
+ };
109
+ type PaymentStatusResult = {
110
+ status: string;
111
+ purchase?: PurchaseResult["purchase"] | null;
112
+ };
113
+ interface CheckoutTransport {
114
+ request<T>(path: string, init: {
115
+ method?: string;
116
+ body?: unknown;
117
+ sessionToken?: string | null;
118
+ }): Promise<T>;
119
+ }
120
+
121
+ type TickeanClient = ReturnType<typeof createTickean>;
122
+ declare function createTickean(options: CreateTickeanOptions): {
123
+ readonly session: CheckoutSession | null;
124
+ createSession(params: {
125
+ eventSlug: string;
126
+ returnUrl?: string;
127
+ }): Promise<CheckoutSession>;
128
+ getCatalog(): Promise<PublicEvent>;
129
+ quote(params: {
130
+ items: CartItem[];
131
+ discountCode?: string;
132
+ showId?: string;
133
+ }): Promise<QuoteResult>;
134
+ sendOtp(params: {
135
+ phone: string;
136
+ channel?: string;
137
+ }): Promise<unknown>;
138
+ verifyOtp(params: {
139
+ phone: string;
140
+ code: string;
141
+ name?: string;
142
+ email?: string;
143
+ }): Promise<unknown>;
144
+ createPurchase(params: {
145
+ items: CartItem[];
146
+ paymentMethod: string;
147
+ currency: string;
148
+ showId?: string;
149
+ discountCode?: string;
150
+ expectedTotal?: number;
151
+ shoppingCartReference?: string;
152
+ idempotencyKey?: string;
153
+ }): Promise<PurchaseResult>;
154
+ createPayment(params: {
155
+ orderId: string;
156
+ paymentMethod: string;
157
+ currency: string;
158
+ amount: number;
159
+ }): Promise<PaymentResult>;
160
+ getPaymentStatus(): Promise<PaymentStatusResult>;
161
+ watchPayment(options?: {
162
+ intervalMs?: number;
163
+ timeoutMs?: number;
164
+ signal?: AbortSignal;
165
+ }): Promise<PaymentStatusResult>;
166
+ };
167
+
168
+ export { type Buyer, type CartItem, type CheckoutSession, type CheckoutTransport, type CreateTickeanOptions, type PaymentResult, type PaymentStatusResult, type PublicEvent, type PublicShow, type PublicShowOption, type PurchaseResult, type QuoteResult, type TickeanClient, TickeanError, type TickeanErrorPayload, createTickean };
@@ -0,0 +1,168 @@
1
+ type TickeanErrorPayload = {
2
+ code: string;
3
+ message: string;
4
+ details?: Record<string, unknown>;
5
+ };
6
+ declare class TickeanError extends Error {
7
+ readonly code: string;
8
+ readonly details?: Record<string, unknown>;
9
+ readonly status?: number;
10
+ constructor(payload: TickeanErrorPayload, status?: number);
11
+ }
12
+ type CreateTickeanOptions = {
13
+ publishableKey: string;
14
+ apiBaseUrl?: string;
15
+ /** When true, uses an in-memory mock adapter (no network). */
16
+ demo?: boolean;
17
+ fetchImpl?: typeof fetch;
18
+ };
19
+ type CheckoutSession = {
20
+ sessionId: string;
21
+ sessionToken: string;
22
+ expiresAt: string;
23
+ event: PublicEvent;
24
+ capabilities: Record<string, boolean>;
25
+ };
26
+ type PublicShowOption = {
27
+ id: string;
28
+ name?: string;
29
+ description?: string;
30
+ price: number;
31
+ currency?: string;
32
+ stock?: number;
33
+ maxPerPurchase?: number;
34
+ optionType?: string;
35
+ accessScope?: string;
36
+ coveredShowIds?: string[];
37
+ passIssuanceMode?: "PER_DAY" | "SINGLE_PASS";
38
+ catalogVisibility?: "PUBLIC" | "PROMO_GATED";
39
+ promotionNxM?: unknown;
40
+ quantityDiscount?: unknown;
41
+ status?: string;
42
+ };
43
+ type PublicShow = {
44
+ id: string;
45
+ title?: string;
46
+ date?: string;
47
+ endDate?: string;
48
+ showOptions: PublicShowOption[];
49
+ };
50
+ type PublicEvent = {
51
+ id: string;
52
+ slug: string;
53
+ title: string;
54
+ description?: string;
55
+ images?: unknown[];
56
+ location?: unknown;
57
+ availablePaymentMethods?: string[];
58
+ organization?: {
59
+ id?: string;
60
+ slug?: string;
61
+ name?: string;
62
+ logo?: string | null;
63
+ } | null;
64
+ shows: PublicShow[];
65
+ };
66
+ type CartItem = {
67
+ showOptionId: string;
68
+ amount: number;
69
+ };
70
+ type QuoteResult = {
71
+ valid: boolean;
72
+ totalPrice: number;
73
+ pricingBreakdown?: unknown;
74
+ discountCode?: unknown;
75
+ unlockedShowOptionIds?: string[];
76
+ unlockedShowOptions?: PublicShowOption[];
77
+ };
78
+ type Buyer = {
79
+ id: string;
80
+ phone: string;
81
+ name?: string;
82
+ email?: string;
83
+ };
84
+ type PurchaseResult = {
85
+ purchase: {
86
+ id: string;
87
+ status: string;
88
+ totalPrice: number;
89
+ currency: string;
90
+ shoppingCartReference: string;
91
+ paymentMethod?: string;
92
+ };
93
+ shoppingCartReference: string;
94
+ cartSessionToken: string;
95
+ };
96
+ type PaymentResult = {
97
+ id: string;
98
+ paymentStatus?: string;
99
+ paymentMethod?: string;
100
+ paymentInstructions?: unknown;
101
+ redirectUrl?: string;
102
+ initPoint?: string;
103
+ returnUrl?: string | null;
104
+ stripe?: unknown;
105
+ fintocWidget?: unknown;
106
+ dlocalGo?: unknown;
107
+ airwallex?: unknown;
108
+ };
109
+ type PaymentStatusResult = {
110
+ status: string;
111
+ purchase?: PurchaseResult["purchase"] | null;
112
+ };
113
+ interface CheckoutTransport {
114
+ request<T>(path: string, init: {
115
+ method?: string;
116
+ body?: unknown;
117
+ sessionToken?: string | null;
118
+ }): Promise<T>;
119
+ }
120
+
121
+ type TickeanClient = ReturnType<typeof createTickean>;
122
+ declare function createTickean(options: CreateTickeanOptions): {
123
+ readonly session: CheckoutSession | null;
124
+ createSession(params: {
125
+ eventSlug: string;
126
+ returnUrl?: string;
127
+ }): Promise<CheckoutSession>;
128
+ getCatalog(): Promise<PublicEvent>;
129
+ quote(params: {
130
+ items: CartItem[];
131
+ discountCode?: string;
132
+ showId?: string;
133
+ }): Promise<QuoteResult>;
134
+ sendOtp(params: {
135
+ phone: string;
136
+ channel?: string;
137
+ }): Promise<unknown>;
138
+ verifyOtp(params: {
139
+ phone: string;
140
+ code: string;
141
+ name?: string;
142
+ email?: string;
143
+ }): Promise<unknown>;
144
+ createPurchase(params: {
145
+ items: CartItem[];
146
+ paymentMethod: string;
147
+ currency: string;
148
+ showId?: string;
149
+ discountCode?: string;
150
+ expectedTotal?: number;
151
+ shoppingCartReference?: string;
152
+ idempotencyKey?: string;
153
+ }): Promise<PurchaseResult>;
154
+ createPayment(params: {
155
+ orderId: string;
156
+ paymentMethod: string;
157
+ currency: string;
158
+ amount: number;
159
+ }): Promise<PaymentResult>;
160
+ getPaymentStatus(): Promise<PaymentStatusResult>;
161
+ watchPayment(options?: {
162
+ intervalMs?: number;
163
+ timeoutMs?: number;
164
+ signal?: AbortSignal;
165
+ }): Promise<PaymentStatusResult>;
166
+ };
167
+
168
+ export { type Buyer, type CartItem, type CheckoutSession, type CheckoutTransport, type CreateTickeanOptions, type PaymentResult, type PaymentStatusResult, type PublicEvent, type PublicShow, type PublicShowOption, type PurchaseResult, type QuoteResult, type TickeanClient, TickeanError, type TickeanErrorPayload, createTickean };
package/dist/index.js ADDED
@@ -0,0 +1,398 @@
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
+ TickeanError: () => TickeanError,
24
+ createTickean: () => createTickean
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/demo.ts
29
+ var demoEvent = {
30
+ id: "evt_demo",
31
+ slug: "demo-festival",
32
+ title: "Demo Festival",
33
+ description: "Sample multi-day festival for local SDK demos.",
34
+ availablePaymentMethods: ["TRANSFER", "MERCADOPAGO"],
35
+ organization: {
36
+ id: "org_demo",
37
+ slug: "demo-org",
38
+ name: "Demo Organizer",
39
+ logo: null
40
+ },
41
+ shows: [
42
+ {
43
+ id: "show_sat",
44
+ title: "S\xE1bado",
45
+ date: new Date(Date.now() + 7 * 864e5).toISOString(),
46
+ showOptions: [
47
+ {
48
+ id: "opt_day",
49
+ name: "Entrada general",
50
+ price: 15e3,
51
+ currency: "ARS",
52
+ stock: 100,
53
+ maxPerPurchase: 6,
54
+ optionType: "TICKET",
55
+ accessScope: "SINGLE_SHOW",
56
+ catalogVisibility: "PUBLIC",
57
+ passIssuanceMode: "PER_DAY"
58
+ }
59
+ ]
60
+ },
61
+ {
62
+ id: "show_sun",
63
+ title: "Domingo",
64
+ date: new Date(Date.now() + 8 * 864e5).toISOString(),
65
+ showOptions: [
66
+ {
67
+ id: "opt_pass",
68
+ name: "Abono 2 d\xEDas",
69
+ price: 25e3,
70
+ currency: "ARS",
71
+ stock: 50,
72
+ maxPerPurchase: 4,
73
+ optionType: "TICKET",
74
+ accessScope: "ALL_EVENT_SHOWS",
75
+ coveredShowIds: ["show_sat", "show_sun"],
76
+ passIssuanceMode: "SINGLE_PASS",
77
+ catalogVisibility: "PUBLIC"
78
+ },
79
+ {
80
+ id: "opt_gated",
81
+ name: "Promo 2x1 (c\xF3digo)",
82
+ price: 15e3,
83
+ currency: "ARS",
84
+ stock: 30,
85
+ maxPerPurchase: 4,
86
+ optionType: "TICKET",
87
+ accessScope: "SINGLE_SHOW",
88
+ catalogVisibility: "PROMO_GATED",
89
+ promotionNxM: { enabled: true, buyQty: 2, payQty: 1 }
90
+ }
91
+ ]
92
+ }
93
+ ]
94
+ };
95
+ function createDemoTransport() {
96
+ let sessionToken = "demo_session_token";
97
+ let buyer = null;
98
+ let unlocked = [];
99
+ let purchaseId = "purchase_demo";
100
+ let cartRef = "cart_demo";
101
+ return {
102
+ async request(path, init) {
103
+ if (path === "/v1/checkout/sessions" && init.method === "POST") {
104
+ sessionToken = `demo_${Date.now()}`;
105
+ return {
106
+ sessionId: "sess_demo",
107
+ sessionToken,
108
+ expiresAt: new Date(Date.now() + 36e5).toISOString(),
109
+ event: {
110
+ ...demoEvent,
111
+ shows: demoEvent.shows.map((show) => ({
112
+ ...show,
113
+ showOptions: show.showOptions.filter(
114
+ (o) => o.catalogVisibility !== "PROMO_GATED"
115
+ )
116
+ }))
117
+ },
118
+ capabilities: {
119
+ tickets: true,
120
+ discounts: true,
121
+ transfer: true,
122
+ onlinePayments: true
123
+ }
124
+ };
125
+ }
126
+ if (path === "/v1/checkout/catalog") {
127
+ 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
+ }))
140
+ };
141
+ }
142
+ if (path === "/v1/checkout/quote" && init.method === "POST") {
143
+ const body = init.body;
144
+ if (body.discountCode?.toUpperCase() === "DEMO2X1") {
145
+ unlocked = demoEvent.shows.flatMap((s) => s.showOptions).filter((o) => o.catalogVisibility === "PROMO_GATED");
146
+ }
147
+ const allOptions = demoEvent.shows.flatMap((s) => s.showOptions);
148
+ const total = body.items.reduce((sum, item) => {
149
+ const opt = allOptions.find((o) => o.id === item.showOptionId);
150
+ return sum + Number(opt?.price || 0) * item.amount;
151
+ }, 0);
152
+ return {
153
+ valid: true,
154
+ totalPrice: total,
155
+ pricingBreakdown: { subtotal: total },
156
+ unlockedShowOptionIds: unlocked.map((o) => o.id),
157
+ unlockedShowOptions: unlocked
158
+ };
159
+ }
160
+ if (path === "/v1/checkout/otp/send") {
161
+ return { sent: true };
162
+ }
163
+ if (path === "/v1/checkout/otp/verify") {
164
+ const body = init.body;
165
+ buyer = {
166
+ id: "buyer_demo",
167
+ phone: body.phone,
168
+ name: body.name || "Demo Buyer",
169
+ email: body.email
170
+ };
171
+ return { verified: true, buyer };
172
+ }
173
+ if (path === "/v1/checkout/purchases") {
174
+ if (!buyer) {
175
+ throw Object.assign(new Error("OTP required"), {
176
+ code: "checkout_otp_required"
177
+ });
178
+ }
179
+ const body = init.body;
180
+ const allOptions = demoEvent.shows.flatMap((s) => s.showOptions);
181
+ const total = body.items.reduce((sum, item) => {
182
+ const opt = allOptions.find((o) => o.id === item.showOptionId);
183
+ return sum + Number(opt?.price || 0) * item.amount;
184
+ }, 0);
185
+ purchaseId = `purchase_${Date.now()}`;
186
+ cartRef = `cart_${Date.now()}`;
187
+ return {
188
+ purchase: {
189
+ id: purchaseId,
190
+ status: "PENDING",
191
+ totalPrice: total,
192
+ currency: body.currency,
193
+ shoppingCartReference: cartRef,
194
+ paymentMethod: body.paymentMethod
195
+ },
196
+ shoppingCartReference: cartRef,
197
+ cartSessionToken: "demo_cart_token"
198
+ };
199
+ }
200
+ 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
212
+ };
213
+ }
214
+ if (path === "/v1/checkout/payments/status") {
215
+ return {
216
+ status: "PENDING",
217
+ purchase: {
218
+ id: purchaseId,
219
+ status: "PENDING",
220
+ totalPrice: 0,
221
+ currency: "ARS",
222
+ shoppingCartReference: cartRef
223
+ }
224
+ };
225
+ }
226
+ throw new Error(`Demo transport: unhandled ${init.method || "GET"} ${path}`);
227
+ }
228
+ };
229
+ }
230
+
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
+ // src/http.ts
243
+ function createHttpTransport(options) {
244
+ const fetchImpl = options.fetchImpl || fetch;
245
+ return {
246
+ async request(path, init) {
247
+ const headers = {
248
+ "Content-Type": "application/json",
249
+ "X-Tickean-Key": options.publishableKey
250
+ };
251
+ if (init.sessionToken) {
252
+ headers["X-Tickean-Checkout-Session"] = init.sessionToken;
253
+ }
254
+ const response = await fetchImpl(
255
+ `${options.apiBaseUrl.replace(/\/$/, "")}${path}`,
256
+ {
257
+ method: init.method || "GET",
258
+ headers,
259
+ body: init.body ? JSON.stringify(init.body) : void 0,
260
+ credentials: "omit"
261
+ }
262
+ );
263
+ const json = await response.json().catch(() => ({}));
264
+ if (!response.ok) {
265
+ const err = json?.error || {};
266
+ throw new TickeanError(
267
+ {
268
+ code: err.code || "checkout_request_failed",
269
+ message: err.message || response.statusText || "Request failed",
270
+ details: err.details
271
+ },
272
+ response.status
273
+ );
274
+ }
275
+ return json;
276
+ }
277
+ };
278
+ }
279
+
280
+ // src/index.ts
281
+ function createTickean(options) {
282
+ if (!options.publishableKey && !options.demo) {
283
+ throw new TickeanError({
284
+ code: "checkout_key_required",
285
+ message: "publishableKey is required unless demo mode is enabled"
286
+ });
287
+ }
288
+ const transport = options.demo ? createDemoTransport() : createHttpTransport({
289
+ publishableKey: options.publishableKey,
290
+ apiBaseUrl: options.apiBaseUrl || "https://api.tickean.com",
291
+ fetchImpl: options.fetchImpl
292
+ });
293
+ let session = null;
294
+ const requireSession = () => {
295
+ if (!session?.sessionToken) {
296
+ throw new TickeanError({
297
+ code: "checkout_session_required",
298
+ message: "Call createSession() before using checkout methods"
299
+ });
300
+ }
301
+ return session;
302
+ };
303
+ return {
304
+ get session() {
305
+ return session;
306
+ },
307
+ async createSession(params) {
308
+ session = await transport.request("/v1/checkout/sessions", {
309
+ method: "POST",
310
+ body: params
311
+ });
312
+ return session;
313
+ },
314
+ async getCatalog() {
315
+ const active = requireSession();
316
+ return transport.request("/v1/checkout/catalog", {
317
+ sessionToken: active.sessionToken
318
+ });
319
+ },
320
+ async quote(params) {
321
+ const active = requireSession();
322
+ return transport.request("/v1/checkout/quote", {
323
+ method: "POST",
324
+ body: params,
325
+ sessionToken: active.sessionToken
326
+ });
327
+ },
328
+ async sendOtp(params) {
329
+ const active = requireSession();
330
+ return transport.request("/v1/checkout/otp/send", {
331
+ method: "POST",
332
+ body: params,
333
+ sessionToken: active.sessionToken
334
+ });
335
+ },
336
+ async verifyOtp(params) {
337
+ const active = requireSession();
338
+ return transport.request("/v1/checkout/otp/verify", {
339
+ method: "POST",
340
+ body: params,
341
+ sessionToken: active.sessionToken
342
+ });
343
+ },
344
+ async createPurchase(params) {
345
+ const active = requireSession();
346
+ return transport.request("/v1/checkout/purchases", {
347
+ method: "POST",
348
+ body: params,
349
+ sessionToken: active.sessionToken
350
+ });
351
+ },
352
+ async createPayment(params) {
353
+ const active = requireSession();
354
+ return transport.request("/v1/checkout/payments", {
355
+ method: "POST",
356
+ body: params,
357
+ sessionToken: active.sessionToken
358
+ });
359
+ },
360
+ async getPaymentStatus() {
361
+ const active = requireSession();
362
+ return transport.request("/v1/checkout/payments/status", {
363
+ sessionToken: active.sessionToken
364
+ });
365
+ },
366
+ async watchPayment(options2) {
367
+ const intervalMs = options2?.intervalMs ?? 2500;
368
+ const timeoutMs = options2?.timeoutMs ?? 5 * 60 * 1e3;
369
+ const started = Date.now();
370
+ while (Date.now() - started < timeoutMs) {
371
+ if (options2?.signal?.aborted) {
372
+ throw new TickeanError({
373
+ code: "checkout_aborted",
374
+ message: "Payment watch aborted"
375
+ });
376
+ }
377
+ const status = await this.getPaymentStatus();
378
+ if (["COMPLETED", "CONFIRMED", "PAID"].includes(
379
+ String(status.status || "").toUpperCase()
380
+ ) || ["COMPLETED", "CONFIRMED", "PAID"].includes(
381
+ String(status.purchase?.status || "").toUpperCase()
382
+ )) {
383
+ return status;
384
+ }
385
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
386
+ }
387
+ throw new TickeanError({
388
+ code: "checkout_payment_timeout",
389
+ message: "Timed out waiting for payment confirmation"
390
+ });
391
+ }
392
+ };
393
+ }
394
+ // Annotate the CommonJS export names for ESM import in node:
395
+ 0 && (module.exports = {
396
+ TickeanError,
397
+ createTickean
398
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,370 @@
1
+ // src/demo.ts
2
+ var demoEvent = {
3
+ id: "evt_demo",
4
+ slug: "demo-festival",
5
+ title: "Demo Festival",
6
+ description: "Sample multi-day festival for local SDK demos.",
7
+ availablePaymentMethods: ["TRANSFER", "MERCADOPAGO"],
8
+ organization: {
9
+ id: "org_demo",
10
+ slug: "demo-org",
11
+ name: "Demo Organizer",
12
+ logo: null
13
+ },
14
+ shows: [
15
+ {
16
+ id: "show_sat",
17
+ title: "S\xE1bado",
18
+ date: new Date(Date.now() + 7 * 864e5).toISOString(),
19
+ showOptions: [
20
+ {
21
+ id: "opt_day",
22
+ name: "Entrada general",
23
+ price: 15e3,
24
+ currency: "ARS",
25
+ stock: 100,
26
+ maxPerPurchase: 6,
27
+ optionType: "TICKET",
28
+ accessScope: "SINGLE_SHOW",
29
+ catalogVisibility: "PUBLIC",
30
+ passIssuanceMode: "PER_DAY"
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ id: "show_sun",
36
+ title: "Domingo",
37
+ date: new Date(Date.now() + 8 * 864e5).toISOString(),
38
+ showOptions: [
39
+ {
40
+ id: "opt_pass",
41
+ name: "Abono 2 d\xEDas",
42
+ price: 25e3,
43
+ currency: "ARS",
44
+ stock: 50,
45
+ maxPerPurchase: 4,
46
+ optionType: "TICKET",
47
+ accessScope: "ALL_EVENT_SHOWS",
48
+ coveredShowIds: ["show_sat", "show_sun"],
49
+ passIssuanceMode: "SINGLE_PASS",
50
+ catalogVisibility: "PUBLIC"
51
+ },
52
+ {
53
+ id: "opt_gated",
54
+ name: "Promo 2x1 (c\xF3digo)",
55
+ price: 15e3,
56
+ currency: "ARS",
57
+ stock: 30,
58
+ maxPerPurchase: 4,
59
+ optionType: "TICKET",
60
+ accessScope: "SINGLE_SHOW",
61
+ catalogVisibility: "PROMO_GATED",
62
+ promotionNxM: { enabled: true, buyQty: 2, payQty: 1 }
63
+ }
64
+ ]
65
+ }
66
+ ]
67
+ };
68
+ function createDemoTransport() {
69
+ let sessionToken = "demo_session_token";
70
+ let buyer = null;
71
+ let unlocked = [];
72
+ let purchaseId = "purchase_demo";
73
+ let cartRef = "cart_demo";
74
+ return {
75
+ async request(path, init) {
76
+ if (path === "/v1/checkout/sessions" && init.method === "POST") {
77
+ sessionToken = `demo_${Date.now()}`;
78
+ return {
79
+ sessionId: "sess_demo",
80
+ sessionToken,
81
+ expiresAt: new Date(Date.now() + 36e5).toISOString(),
82
+ event: {
83
+ ...demoEvent,
84
+ shows: demoEvent.shows.map((show) => ({
85
+ ...show,
86
+ showOptions: show.showOptions.filter(
87
+ (o) => o.catalogVisibility !== "PROMO_GATED"
88
+ )
89
+ }))
90
+ },
91
+ capabilities: {
92
+ tickets: true,
93
+ discounts: true,
94
+ transfer: true,
95
+ onlinePayments: true
96
+ }
97
+ };
98
+ }
99
+ if (path === "/v1/checkout/catalog") {
100
+ return {
101
+ ...demoEvent,
102
+ shows: demoEvent.shows.map((show) => ({
103
+ ...show,
104
+ showOptions: [
105
+ ...show.showOptions.filter(
106
+ (o) => o.catalogVisibility !== "PROMO_GATED"
107
+ ),
108
+ ...unlocked.filter(
109
+ (u) => show.showOptions.some((o) => o.id === u.id)
110
+ )
111
+ ]
112
+ }))
113
+ };
114
+ }
115
+ if (path === "/v1/checkout/quote" && init.method === "POST") {
116
+ const body = init.body;
117
+ if (body.discountCode?.toUpperCase() === "DEMO2X1") {
118
+ unlocked = demoEvent.shows.flatMap((s) => s.showOptions).filter((o) => o.catalogVisibility === "PROMO_GATED");
119
+ }
120
+ const allOptions = demoEvent.shows.flatMap((s) => s.showOptions);
121
+ const total = body.items.reduce((sum, item) => {
122
+ const opt = allOptions.find((o) => o.id === item.showOptionId);
123
+ return sum + Number(opt?.price || 0) * item.amount;
124
+ }, 0);
125
+ return {
126
+ valid: true,
127
+ totalPrice: total,
128
+ pricingBreakdown: { subtotal: total },
129
+ unlockedShowOptionIds: unlocked.map((o) => o.id),
130
+ unlockedShowOptions: unlocked
131
+ };
132
+ }
133
+ if (path === "/v1/checkout/otp/send") {
134
+ return { sent: true };
135
+ }
136
+ if (path === "/v1/checkout/otp/verify") {
137
+ const body = init.body;
138
+ buyer = {
139
+ id: "buyer_demo",
140
+ phone: body.phone,
141
+ name: body.name || "Demo Buyer",
142
+ email: body.email
143
+ };
144
+ return { verified: true, buyer };
145
+ }
146
+ if (path === "/v1/checkout/purchases") {
147
+ if (!buyer) {
148
+ throw Object.assign(new Error("OTP required"), {
149
+ code: "checkout_otp_required"
150
+ });
151
+ }
152
+ const body = init.body;
153
+ const allOptions = demoEvent.shows.flatMap((s) => s.showOptions);
154
+ const total = body.items.reduce((sum, item) => {
155
+ const opt = allOptions.find((o) => o.id === item.showOptionId);
156
+ return sum + Number(opt?.price || 0) * item.amount;
157
+ }, 0);
158
+ purchaseId = `purchase_${Date.now()}`;
159
+ cartRef = `cart_${Date.now()}`;
160
+ return {
161
+ purchase: {
162
+ id: purchaseId,
163
+ status: "PENDING",
164
+ totalPrice: total,
165
+ currency: body.currency,
166
+ shoppingCartReference: cartRef,
167
+ paymentMethod: body.paymentMethod
168
+ },
169
+ shoppingCartReference: cartRef,
170
+ cartSessionToken: "demo_cart_token"
171
+ };
172
+ }
173
+ if (path === "/v1/checkout/payments") {
174
+ return {
175
+ id: `pay_${Date.now()}`,
176
+ paymentStatus: "PENDING",
177
+ paymentMethod: init.body?.paymentMethod,
178
+ paymentInstructions: {
179
+ alias: "tickean.demo",
180
+ cvu: "0000003100010000000001",
181
+ amount: init.body?.amount
182
+ },
183
+ redirectUrl: void 0,
184
+ returnUrl: null
185
+ };
186
+ }
187
+ if (path === "/v1/checkout/payments/status") {
188
+ return {
189
+ status: "PENDING",
190
+ purchase: {
191
+ id: purchaseId,
192
+ status: "PENDING",
193
+ totalPrice: 0,
194
+ currency: "ARS",
195
+ shoppingCartReference: cartRef
196
+ }
197
+ };
198
+ }
199
+ throw new Error(`Demo transport: unhandled ${init.method || "GET"} ${path}`);
200
+ }
201
+ };
202
+ }
203
+
204
+ // src/types.ts
205
+ var TickeanError = class extends Error {
206
+ constructor(payload, status) {
207
+ super(payload.message);
208
+ this.name = "TickeanError";
209
+ this.code = payload.code;
210
+ this.details = payload.details;
211
+ this.status = status;
212
+ }
213
+ };
214
+
215
+ // src/http.ts
216
+ function createHttpTransport(options) {
217
+ const fetchImpl = options.fetchImpl || fetch;
218
+ return {
219
+ async request(path, init) {
220
+ const headers = {
221
+ "Content-Type": "application/json",
222
+ "X-Tickean-Key": options.publishableKey
223
+ };
224
+ if (init.sessionToken) {
225
+ headers["X-Tickean-Checkout-Session"] = init.sessionToken;
226
+ }
227
+ const response = await fetchImpl(
228
+ `${options.apiBaseUrl.replace(/\/$/, "")}${path}`,
229
+ {
230
+ method: init.method || "GET",
231
+ headers,
232
+ body: init.body ? JSON.stringify(init.body) : void 0,
233
+ credentials: "omit"
234
+ }
235
+ );
236
+ const json = await response.json().catch(() => ({}));
237
+ if (!response.ok) {
238
+ const err = json?.error || {};
239
+ throw new TickeanError(
240
+ {
241
+ code: err.code || "checkout_request_failed",
242
+ message: err.message || response.statusText || "Request failed",
243
+ details: err.details
244
+ },
245
+ response.status
246
+ );
247
+ }
248
+ return json;
249
+ }
250
+ };
251
+ }
252
+
253
+ // src/index.ts
254
+ function createTickean(options) {
255
+ if (!options.publishableKey && !options.demo) {
256
+ throw new TickeanError({
257
+ code: "checkout_key_required",
258
+ message: "publishableKey is required unless demo mode is enabled"
259
+ });
260
+ }
261
+ const transport = options.demo ? createDemoTransport() : createHttpTransport({
262
+ publishableKey: options.publishableKey,
263
+ apiBaseUrl: options.apiBaseUrl || "https://api.tickean.com",
264
+ fetchImpl: options.fetchImpl
265
+ });
266
+ let session = null;
267
+ const requireSession = () => {
268
+ if (!session?.sessionToken) {
269
+ throw new TickeanError({
270
+ code: "checkout_session_required",
271
+ message: "Call createSession() before using checkout methods"
272
+ });
273
+ }
274
+ return session;
275
+ };
276
+ return {
277
+ get session() {
278
+ return session;
279
+ },
280
+ async createSession(params) {
281
+ session = await transport.request("/v1/checkout/sessions", {
282
+ method: "POST",
283
+ body: params
284
+ });
285
+ return session;
286
+ },
287
+ async getCatalog() {
288
+ const active = requireSession();
289
+ return transport.request("/v1/checkout/catalog", {
290
+ sessionToken: active.sessionToken
291
+ });
292
+ },
293
+ async quote(params) {
294
+ const active = requireSession();
295
+ return transport.request("/v1/checkout/quote", {
296
+ method: "POST",
297
+ body: params,
298
+ sessionToken: active.sessionToken
299
+ });
300
+ },
301
+ async sendOtp(params) {
302
+ const active = requireSession();
303
+ return transport.request("/v1/checkout/otp/send", {
304
+ method: "POST",
305
+ body: params,
306
+ sessionToken: active.sessionToken
307
+ });
308
+ },
309
+ async verifyOtp(params) {
310
+ const active = requireSession();
311
+ return transport.request("/v1/checkout/otp/verify", {
312
+ method: "POST",
313
+ body: params,
314
+ sessionToken: active.sessionToken
315
+ });
316
+ },
317
+ async createPurchase(params) {
318
+ const active = requireSession();
319
+ return transport.request("/v1/checkout/purchases", {
320
+ method: "POST",
321
+ body: params,
322
+ sessionToken: active.sessionToken
323
+ });
324
+ },
325
+ async createPayment(params) {
326
+ const active = requireSession();
327
+ return transport.request("/v1/checkout/payments", {
328
+ method: "POST",
329
+ body: params,
330
+ sessionToken: active.sessionToken
331
+ });
332
+ },
333
+ async getPaymentStatus() {
334
+ const active = requireSession();
335
+ return transport.request("/v1/checkout/payments/status", {
336
+ sessionToken: active.sessionToken
337
+ });
338
+ },
339
+ async watchPayment(options2) {
340
+ const intervalMs = options2?.intervalMs ?? 2500;
341
+ const timeoutMs = options2?.timeoutMs ?? 5 * 60 * 1e3;
342
+ const started = Date.now();
343
+ while (Date.now() - started < timeoutMs) {
344
+ if (options2?.signal?.aborted) {
345
+ throw new TickeanError({
346
+ code: "checkout_aborted",
347
+ message: "Payment watch aborted"
348
+ });
349
+ }
350
+ const status = await this.getPaymentStatus();
351
+ if (["COMPLETED", "CONFIRMED", "PAID"].includes(
352
+ String(status.status || "").toUpperCase()
353
+ ) || ["COMPLETED", "CONFIRMED", "PAID"].includes(
354
+ String(status.purchase?.status || "").toUpperCase()
355
+ )) {
356
+ return status;
357
+ }
358
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
359
+ }
360
+ throw new TickeanError({
361
+ code: "checkout_payment_timeout",
362
+ message: "Timed out waiting for payment confirmation"
363
+ });
364
+ }
365
+ };
366
+ }
367
+ export {
368
+ TickeanError,
369
+ createTickean
370
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@tickean/checkout-js",
3
+ "version": "0.1.0",
4
+ "description": "Tickean Headless Checkout TypeScript client",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
22
+ "test": "vitest run",
23
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
24
+ },
25
+ "devDependencies": {
26
+ "tsup": "^8.4.0",
27
+ "typescript": "^5.8.2",
28
+ "vitest": "^3.0.5"
29
+ }
30
+ }