@cimplify/sdk 0.16.2 → 0.17.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/advanced.d.mts +2 -2
- package/dist/advanced.d.ts +2 -2
- package/dist/advanced.js +90 -2906
- package/dist/advanced.mjs +3 -2886
- package/dist/chunk-FLPI2VVJ.js +72 -0
- package/dist/chunk-G6KNFYJD.mjs +687 -0
- package/dist/chunk-GFKBH7R2.mjs +2759 -0
- package/dist/chunk-J65U5SED.js +714 -0
- package/dist/chunk-LM3CJ4L7.mjs +69 -0
- package/dist/chunk-OITG2SEN.js +692 -0
- package/dist/chunk-OVM5YWM5.mjs +674 -0
- package/dist/chunk-REPTMTQ4.js +2810 -0
- package/dist/chunk-S6TU55KP.js +170 -0
- package/dist/chunk-WDIMISRC.mjs +166 -0
- package/dist/{client-DTTtSSmQ.d.ts → client-BaolRdB4.d.mts} +3 -3
- package/dist/{client-Cb7r8u6t.d.mts → client-C59R79gx.d.ts} +3 -3
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +401 -4412
- package/dist/index.mjs +5 -4317
- package/dist/{payment-Dvd7nRmk.d.mts → payment-C6ULw3K-.d.mts} +1 -1
- package/dist/{payment-Dvd7nRmk.d.ts → payment-C6ULw3K-.d.ts} +1 -1
- package/dist/{price-DrfhwHpw.d.ts → price-Bd6JBV8W.d.ts} +1 -1
- package/dist/{price-BDZNXiLS.d.mts → price-CbR6DCt0.d.mts} +1 -1
- package/dist/react.d.mts +12 -7
- package/dist/react.d.ts +12 -7
- package/dist/react.js +377 -3961
- package/dist/react.mjs +326 -3913
- package/dist/utils.d.mts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +122 -720
- package/dist/utils.mjs +2 -694
- package/package.json +1 -1
- package/registry/add-on-selector.json +1 -1
- package/registry/checkout-page.json +1 -1
- package/registry/composite-selector.json +1 -1
- package/registry/product-grid.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { getVariantDisplayName, createCimplifyClient } from './chunk-G6KNFYJD.mjs';
|
|
3
|
+
export { getVariantDisplayName } from './chunk-G6KNFYJD.mjs';
|
|
4
|
+
import { ELEMENT_TYPES, EVENT_TYPES } from './chunk-GFKBH7R2.mjs';
|
|
5
|
+
import { isSupportedCurrency, formatPrice, parsePrice, isOnSale, getDiscountPercentage, getBasePrice } from './chunk-OVM5YWM5.mjs';
|
|
2
6
|
import React3, { createContext, useContext, useState, useEffect, useRef, useMemo, useSyncExternalStore, useCallback, useId } from 'react';
|
|
3
7
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
4
8
|
import { NumberField } from '@base-ui/react/number-field';
|
|
@@ -13,8 +17,6 @@ import { Field } from '@base-ui/react/field';
|
|
|
13
17
|
import { Input } from '@base-ui/react/input';
|
|
14
18
|
import { Tabs } from '@base-ui/react/tabs';
|
|
15
19
|
|
|
16
|
-
// src/ads/index.tsx
|
|
17
|
-
|
|
18
20
|
// src/ads/identity.ts
|
|
19
21
|
var COOKIE_NAME = "_cimplify_uid";
|
|
20
22
|
var COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
@@ -277,3753 +279,99 @@ function Ad({
|
|
|
277
279
|
}
|
|
278
280
|
onImpression?.(ad.id);
|
|
279
281
|
}
|
|
280
|
-
},
|
|
281
|
-
{ threshold: 0.5 }
|
|
282
|
-
);
|
|
283
|
-
if (containerRef.current) {
|
|
284
|
-
observer.observe(containerRef.current);
|
|
285
|
-
}
|
|
286
|
-
return () => observer.disconnect();
|
|
287
|
-
}, [ad, siteId, slot, onImpression, identity, apiBase]);
|
|
288
|
-
if (isLoading) {
|
|
289
|
-
return /* @__PURE__ */ jsx(
|
|
290
|
-
"div",
|
|
291
|
-
{
|
|
292
|
-
className: `cimplify-ad cimplify-ad--loading ${className || ""}`,
|
|
293
|
-
style,
|
|
294
|
-
"aria-hidden": "true"
|
|
295
|
-
}
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
if (!config?.enabled) return null;
|
|
299
|
-
if (error) return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
|
|
300
|
-
if (!ad) return null;
|
|
301
|
-
const handleClick = () => {
|
|
302
|
-
onClick?.(ad.id);
|
|
303
|
-
};
|
|
304
|
-
const positionStyles = position === "fixed" ? { position: "fixed", bottom: 0, left: 0, right: 0, zIndex: 9999 } : position === "sticky" ? { position: "sticky", top: 20 } : {};
|
|
305
|
-
return /* @__PURE__ */ jsx(
|
|
306
|
-
"div",
|
|
307
|
-
{
|
|
308
|
-
ref: containerRef,
|
|
309
|
-
id: `cimplify-ad-${ad.id}`,
|
|
310
|
-
className: `cimplify-ad cimplify-ad--${slot} cimplify-ad--${position} cimplify-ad--${config.theme} ${className || ""}`,
|
|
311
|
-
style: { ...positionStyles, ...style },
|
|
312
|
-
onClick: handleClick,
|
|
313
|
-
dangerouslySetInnerHTML: { __html: ad.html }
|
|
314
|
-
}
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// src/utils/cart-transform.ts
|
|
319
|
-
function getSelections(item) {
|
|
320
|
-
if (item.bundle_resolved?.selections?.length) {
|
|
321
|
-
return item.bundle_resolved.selections.map((s) => ({
|
|
322
|
-
name: s.product_name || s.component_id,
|
|
323
|
-
quantity: s.quantity,
|
|
324
|
-
variant_name: s.variant_name
|
|
325
|
-
}));
|
|
326
|
-
}
|
|
327
|
-
if (item.composite_resolved?.selections?.length) {
|
|
328
|
-
return item.composite_resolved.selections.map((s) => ({
|
|
329
|
-
name: s.component_name || s.component_id,
|
|
330
|
-
quantity: s.quantity
|
|
331
|
-
}));
|
|
332
|
-
}
|
|
333
|
-
return void 0;
|
|
334
|
-
}
|
|
335
|
-
function mapItem(item, fx) {
|
|
336
|
-
const amt = (value) => fx ? String(fx.convertPrice(value)) : String(value);
|
|
337
|
-
const result = {
|
|
338
|
-
name: item.name,
|
|
339
|
-
quantity: item.quantity,
|
|
340
|
-
unit_price: amt(item.base_price),
|
|
341
|
-
total_price: amt(item.total_price),
|
|
342
|
-
line_type: item.line_type
|
|
343
|
-
};
|
|
344
|
-
if (item.image_url) result.image_url = item.image_url;
|
|
345
|
-
const variantName = item.variant_info?.name || item.variant_name || void 0;
|
|
346
|
-
if (variantName) result.variant_name = variantName;
|
|
347
|
-
if (item.scheduled_start) result.scheduled_start = item.scheduled_start;
|
|
348
|
-
if (item.scheduled_end) result.scheduled_end = item.scheduled_end;
|
|
349
|
-
const selections = getSelections(item);
|
|
350
|
-
if (selections) result.selections = selections;
|
|
351
|
-
if (item.add_on_options?.length) {
|
|
352
|
-
result.add_ons = item.add_on_options.map((opt) => ({
|
|
353
|
-
name: opt.name,
|
|
354
|
-
price: amt(opt.price ?? "0")
|
|
355
|
-
}));
|
|
356
|
-
}
|
|
357
|
-
if (item.special_instructions) {
|
|
358
|
-
result.special_instructions = item.special_instructions;
|
|
359
|
-
}
|
|
360
|
-
return result;
|
|
361
|
-
}
|
|
362
|
-
function transformToCheckoutCart(cart, fx) {
|
|
363
|
-
const amt = (value) => fx ? String(fx.convertPrice(value)) : String(value);
|
|
364
|
-
return {
|
|
365
|
-
items: cart.items.map((item) => mapItem(item, fx)),
|
|
366
|
-
subtotal: amt(cart.pricing.subtotal),
|
|
367
|
-
tax_amount: amt(cart.pricing.tax_amount),
|
|
368
|
-
total_discounts: amt(cart.pricing.total_discounts),
|
|
369
|
-
service_charge: amt(cart.pricing.service_charge),
|
|
370
|
-
total: amt(cart.pricing.total_price),
|
|
371
|
-
currency: fx?.displayCurrency ?? cart.pricing.currency
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// src/types/common.ts
|
|
376
|
-
function money(value) {
|
|
377
|
-
return typeof value === "string" ? parseFloat(value) || 0 : value;
|
|
378
|
-
}
|
|
379
|
-
function moneyFromNumber(value) {
|
|
380
|
-
return value;
|
|
381
|
-
}
|
|
382
|
-
var SUPPORTED_CURRENCY_CODES = /* @__PURE__ */ new Set([
|
|
383
|
-
"USD",
|
|
384
|
-
"EUR",
|
|
385
|
-
"GBP",
|
|
386
|
-
"JPY",
|
|
387
|
-
"CNY",
|
|
388
|
-
"CHF",
|
|
389
|
-
"CAD",
|
|
390
|
-
"AUD",
|
|
391
|
-
"GHS",
|
|
392
|
-
"NGN",
|
|
393
|
-
"KES",
|
|
394
|
-
"ZAR",
|
|
395
|
-
"XOF",
|
|
396
|
-
"XAF",
|
|
397
|
-
"EGP",
|
|
398
|
-
"TZS",
|
|
399
|
-
"UGX",
|
|
400
|
-
"RWF",
|
|
401
|
-
"ETB",
|
|
402
|
-
"ZMW",
|
|
403
|
-
"BWP",
|
|
404
|
-
"MUR",
|
|
405
|
-
"NAD",
|
|
406
|
-
"MWK",
|
|
407
|
-
"AOA",
|
|
408
|
-
"CDF",
|
|
409
|
-
"GMD",
|
|
410
|
-
"GNF",
|
|
411
|
-
"LRD",
|
|
412
|
-
"SLL",
|
|
413
|
-
"MZN",
|
|
414
|
-
"BIF",
|
|
415
|
-
"INR",
|
|
416
|
-
"BRL",
|
|
417
|
-
"MXN",
|
|
418
|
-
"KRW",
|
|
419
|
-
"TRY",
|
|
420
|
-
"THB",
|
|
421
|
-
"MYR",
|
|
422
|
-
"PHP",
|
|
423
|
-
"IDR",
|
|
424
|
-
"VND",
|
|
425
|
-
"SGD",
|
|
426
|
-
"HKD",
|
|
427
|
-
"TWD",
|
|
428
|
-
"AED",
|
|
429
|
-
"SAR",
|
|
430
|
-
"ILS"
|
|
431
|
-
]);
|
|
432
|
-
function isSupportedCurrency(code) {
|
|
433
|
-
return SUPPORTED_CURRENCY_CODES.has(code);
|
|
434
|
-
}
|
|
435
|
-
function currencyCode(value) {
|
|
436
|
-
return value;
|
|
437
|
-
}
|
|
438
|
-
var ErrorCode = {
|
|
439
|
-
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
440
|
-
NETWORK_ERROR: "NETWORK_ERROR",
|
|
441
|
-
TIMEOUT: "TIMEOUT",
|
|
442
|
-
UNAUTHORIZED: "UNAUTHORIZED",
|
|
443
|
-
NOT_FOUND: "NOT_FOUND"};
|
|
444
|
-
var DOCS_ERROR_BASE_URL = "https://docs.cimplify.io/reference/error-codes";
|
|
445
|
-
function docsUrlForCode(code) {
|
|
446
|
-
return `${DOCS_ERROR_BASE_URL}#${code.toLowerCase().replace(/_/g, "-")}`;
|
|
447
|
-
}
|
|
448
|
-
var ERROR_SUGGESTIONS = {
|
|
449
|
-
UNKNOWN_ERROR: "An unexpected error occurred. Capture the request/response payload and retry with exponential backoff.",
|
|
450
|
-
NETWORK_ERROR: "Check the shopper's connection and retry. If this persists, inspect CORS, DNS, and API reachability.",
|
|
451
|
-
TIMEOUT: "The request exceeded the timeout. Retry once, then poll order status before charging again.",
|
|
452
|
-
UNAUTHORIZED: "Authentication is missing or expired. Ensure a valid access token is set and refresh the session if needed.",
|
|
453
|
-
FORBIDDEN: "The key/session lacks permission for this resource. Verify business ownership and API key scope.",
|
|
454
|
-
NOT_FOUND: "The requested resource does not exist or is not visible in this environment.",
|
|
455
|
-
VALIDATION_ERROR: "One or more fields are invalid. Validate required fields and enum values before retrying.",
|
|
456
|
-
CART_EMPTY: "The cart has no items. Redirect back to menu/catalogue and require at least one line item.",
|
|
457
|
-
CART_EXPIRED: "This cart is no longer active. Recreate a new cart and re-add shopper selections.",
|
|
458
|
-
CART_NOT_FOUND: "Cart could not be located. It may have expired or belongs to a different key/location.",
|
|
459
|
-
ITEM_UNAVAILABLE: "The selected item is unavailable at this location/time. Prompt the shopper to pick an alternative.",
|
|
460
|
-
VARIANT_NOT_FOUND: "The requested variant no longer exists. Refresh product data and require re-selection.",
|
|
461
|
-
VARIANT_OUT_OF_STOCK: "The selected variant is out of stock. Show in-stock variants and block checkout for this line.",
|
|
462
|
-
ADDON_REQUIRED: "A required add-on is missing. Ensure required modifier groups are completed before add-to-cart.",
|
|
463
|
-
ADDON_MAX_EXCEEDED: "Too many add-ons were selected. Enforce max selections client-side before submission.",
|
|
464
|
-
CHECKOUT_VALIDATION_FAILED: "Checkout payload failed validation. Verify customer, order type, and address fields are complete.",
|
|
465
|
-
DELIVERY_ADDRESS_REQUIRED: "Delivery orders require an address. Collect and pass address info before processing checkout.",
|
|
466
|
-
CUSTOMER_INFO_REQUIRED: "Customer details are required. Ensure name/email/phone are available before checkout.",
|
|
467
|
-
QUOTE_NOT_FOUND: "Quote could not be found. Refresh pricing and create a new quote before checkout.",
|
|
468
|
-
QUOTE_EXPIRED: "Quote has expired. Re-fetch pricing to generate a new quote with a valid expiry window.",
|
|
469
|
-
QUOTE_CONSUMED: "Quote has already been used. Request a fresh quote to prevent duplicate checkout attempts.",
|
|
470
|
-
QUOTE_STORAGE_UNAVAILABLE: "Quote storage is temporarily unavailable. Retry shortly and avoid charging until quote fetch succeeds.",
|
|
471
|
-
PAYMENT_FAILED: "Payment provider rejected or failed processing. Show retry/change-method options to the shopper.",
|
|
472
|
-
PAYMENT_CANCELLED: "Payment was cancelled by the shopper or provider flow. Allow a safe retry path.",
|
|
473
|
-
INSUFFICIENT_FUNDS: "Payment method has insufficient funds. Prompt shopper to use another method.",
|
|
474
|
-
CARD_DECLINED: "Card was declined. Ask shopper to retry or switch payment method.",
|
|
475
|
-
INVALID_OTP: "Authorization code is invalid. Let shopper re-enter OTP/PIN and retry.",
|
|
476
|
-
OTP_EXPIRED: "Authorization code expired. Request a new OTP and re-submit authorization.",
|
|
477
|
-
AUTHORIZATION_FAILED: "Additional payment authorization failed. Retry authorization or change payment method.",
|
|
478
|
-
PAYMENT_ACTION_NOT_COMPLETED: "Required payment action was not completed. Resume provider flow and poll for status.",
|
|
479
|
-
SLOT_UNAVAILABLE: "Selected schedule slot is unavailable. Refresh available slots and ask shopper to reselect.",
|
|
480
|
-
BOOKING_CONFLICT: "The requested booking conflicts with an existing reservation. Pick another slot/resource.",
|
|
481
|
-
SERVICE_NOT_FOUND: "Requested service no longer exists. Refresh service catalogue and retry selection.",
|
|
482
|
-
OUT_OF_STOCK: "Inventory is depleted for this item. Remove it or reduce quantity before checkout.",
|
|
483
|
-
INSUFFICIENT_QUANTITY: "Requested quantity exceeds available inventory. Reduce quantity and retry.",
|
|
484
|
-
BUSINESS_ID_REQUIRED: "Business context could not be resolved. Verify the public key and business bootstrap call.",
|
|
485
|
-
INVALID_CART: "Cart is invalid for checkout. Sync cart state, ensure items exist, then retry.",
|
|
486
|
-
ORDER_TYPE_REQUIRED: "Order type is required. Provide one of delivery, pickup, or dine_in before checkout.",
|
|
487
|
-
NO_PAYMENT_ELEMENT: "PaymentElement is required for processCheckout(). Mount it before triggering checkout.",
|
|
488
|
-
PAYMENT_NOT_MOUNTED: "PaymentElement iframe is not mounted. Mount it in the DOM before processCheckout().",
|
|
489
|
-
AUTH_INCOMPLETE: "AuthElement has not completed authentication. Wait for AUTHENTICATED before checkout.",
|
|
490
|
-
AUTH_LOST: "Session was cleared during checkout. Re-authenticate and restart checkout safely.",
|
|
491
|
-
ALREADY_PROCESSING: "Checkout is already in progress. Disable duplicate submits until completion.",
|
|
492
|
-
CHECKOUT_NOT_READY: "Checkout elements are still initializing. Wait for readiness before submit.",
|
|
493
|
-
CANCELLED: "Checkout was cancelled. Preserve cart state and allow shopper to retry.",
|
|
494
|
-
REQUEST_TIMEOUT: "Provider call timed out. Poll payment/order status before issuing another charge attempt.",
|
|
495
|
-
POPUP_BLOCKED: "Browser blocked provider popup. Ask shopper to enable popups and retry.",
|
|
496
|
-
FX_QUOTE_FAILED: "Failed to lock FX quote. Retry currency quote or fallback to base currency."
|
|
497
|
-
};
|
|
498
|
-
var ERROR_HINTS = Object.fromEntries(
|
|
499
|
-
Object.entries(ERROR_SUGGESTIONS).map(([code, suggestion]) => [
|
|
500
|
-
code,
|
|
501
|
-
{
|
|
502
|
-
docs_url: docsUrlForCode(code),
|
|
503
|
-
suggestion
|
|
504
|
-
}
|
|
505
|
-
])
|
|
506
|
-
);
|
|
507
|
-
var CimplifyError = class extends Error {
|
|
508
|
-
constructor(code, message, retryable = false, docs_url, suggestion) {
|
|
509
|
-
super(message);
|
|
510
|
-
this.code = code;
|
|
511
|
-
this.retryable = retryable;
|
|
512
|
-
this.docs_url = docs_url;
|
|
513
|
-
this.suggestion = suggestion;
|
|
514
|
-
this.name = "CimplifyError";
|
|
515
|
-
}
|
|
516
|
-
get userMessage() {
|
|
517
|
-
return this.message;
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
function getErrorHint(code) {
|
|
521
|
-
return ERROR_HINTS[code];
|
|
522
|
-
}
|
|
523
|
-
function enrichError(error, options = {}) {
|
|
524
|
-
const hint = getErrorHint(error.code);
|
|
525
|
-
if (hint) {
|
|
526
|
-
if (!error.docs_url) {
|
|
527
|
-
error.docs_url = hint.docs_url;
|
|
528
|
-
}
|
|
529
|
-
if (!error.suggestion) {
|
|
530
|
-
error.suggestion = hint.suggestion;
|
|
531
|
-
}
|
|
532
|
-
} else if (!error.docs_url) {
|
|
533
|
-
error.docs_url = docsUrlForCode(error.code || ErrorCode.UNKNOWN_ERROR);
|
|
534
|
-
}
|
|
535
|
-
if (options.isTestMode && !error.message.includes("pk_test_")) {
|
|
536
|
-
error.message = `${error.message}
|
|
537
|
-
|
|
538
|
-
\u2139 Your API key is a test-mode key (pk_test_...). Verify test data/session before retrying.`;
|
|
539
|
-
}
|
|
540
|
-
return error;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// src/types/result.ts
|
|
544
|
-
function ok(value) {
|
|
545
|
-
return { ok: true, value };
|
|
546
|
-
}
|
|
547
|
-
function err(error) {
|
|
548
|
-
return { ok: false, error };
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// src/internal/safe.ts
|
|
552
|
-
function toCimplifyError(error) {
|
|
553
|
-
if (error instanceof CimplifyError) return enrichError(error);
|
|
554
|
-
if (error instanceof Error) {
|
|
555
|
-
return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, error.message, false));
|
|
556
|
-
}
|
|
557
|
-
return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, String(error), false));
|
|
558
|
-
}
|
|
559
|
-
async function safe(promise) {
|
|
560
|
-
try {
|
|
561
|
-
return ok(await promise);
|
|
562
|
-
} catch (error) {
|
|
563
|
-
return err(toCimplifyError(error));
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// src/catalogue.ts
|
|
568
|
-
function withQuery(path, params) {
|
|
569
|
-
const query = new URLSearchParams();
|
|
570
|
-
for (const [key, value] of Object.entries(params)) {
|
|
571
|
-
if (value === void 0) continue;
|
|
572
|
-
query.set(key, String(value));
|
|
573
|
-
}
|
|
574
|
-
const queryString = query.toString();
|
|
575
|
-
return queryString ? `${path}?${queryString}` : path;
|
|
576
|
-
}
|
|
577
|
-
function isRecord(value) {
|
|
578
|
-
return typeof value === "object" && value !== null;
|
|
579
|
-
}
|
|
580
|
-
function readFinalPrice(value) {
|
|
581
|
-
if (!isRecord(value)) return void 0;
|
|
582
|
-
const finalPrice = value.final_price;
|
|
583
|
-
if (typeof finalPrice === "string" || typeof finalPrice === "number") {
|
|
584
|
-
return finalPrice;
|
|
585
|
-
}
|
|
586
|
-
return void 0;
|
|
587
|
-
}
|
|
588
|
-
function normalizeAddOnPayload(addOn) {
|
|
589
|
-
if (!isRecord(addOn)) return addOn;
|
|
590
|
-
const normalizedAddOn = { ...addOn };
|
|
591
|
-
const options = normalizedAddOn["options"];
|
|
592
|
-
if (!Array.isArray(options)) return normalizedAddOn;
|
|
593
|
-
normalizedAddOn["options"] = options.map((option) => {
|
|
594
|
-
if (!isRecord(option)) return option;
|
|
595
|
-
const normalizedOption = { ...option };
|
|
596
|
-
const optionPrice = normalizedOption["default_price"];
|
|
597
|
-
if (optionPrice === void 0 || optionPrice === null || optionPrice === "") {
|
|
598
|
-
normalizedOption["default_price"] = readFinalPrice(normalizedOption["default_price_info"]) || readFinalPrice(normalizedOption["price_info"]) || "0";
|
|
599
|
-
}
|
|
600
|
-
return normalizedOption;
|
|
601
|
-
});
|
|
602
|
-
return normalizedAddOn;
|
|
603
|
-
}
|
|
604
|
-
function normalizeCatalogueProductPayload(product) {
|
|
605
|
-
const normalized = { ...product };
|
|
606
|
-
const defaultPrice = normalized["default_price"];
|
|
607
|
-
if (defaultPrice === void 0 || defaultPrice === null || defaultPrice === "") {
|
|
608
|
-
const derivedDefaultPrice = readFinalPrice(normalized["default_price_info"]) || readFinalPrice(normalized["price_info"]) || (typeof normalized["final_price"] === "string" || typeof normalized["final_price"] === "number" ? normalized["final_price"] : void 0);
|
|
609
|
-
normalized["default_price"] = derivedDefaultPrice ?? "0";
|
|
610
|
-
}
|
|
611
|
-
const addOns = normalized["add_ons"];
|
|
612
|
-
if (Array.isArray(addOns)) {
|
|
613
|
-
normalized["add_ons"] = addOns.map((addOn) => normalizeAddOnPayload(addOn));
|
|
614
|
-
}
|
|
615
|
-
return normalized;
|
|
616
|
-
}
|
|
617
|
-
function toFiniteNumber(value) {
|
|
618
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
619
|
-
return value;
|
|
620
|
-
}
|
|
621
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
622
|
-
const parsed = Number(value);
|
|
623
|
-
if (Number.isFinite(parsed)) {
|
|
624
|
-
return parsed;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
return void 0;
|
|
628
|
-
}
|
|
629
|
-
function normalizePagination(value) {
|
|
630
|
-
if (!isRecord(value)) {
|
|
631
|
-
return void 0;
|
|
632
|
-
}
|
|
633
|
-
const totalCount = toFiniteNumber(value.total_count);
|
|
634
|
-
const currentPage = toFiniteNumber(value.current_page);
|
|
635
|
-
const pageSize = toFiniteNumber(value.page_size);
|
|
636
|
-
const totalPages = toFiniteNumber(value.total_pages);
|
|
637
|
-
if (totalCount === void 0 || currentPage === void 0 || pageSize === void 0 || totalPages === void 0) {
|
|
638
|
-
return void 0;
|
|
639
|
-
}
|
|
640
|
-
return {
|
|
641
|
-
total_count: totalCount,
|
|
642
|
-
current_page: currentPage,
|
|
643
|
-
page_size: pageSize,
|
|
644
|
-
total_pages: totalPages,
|
|
645
|
-
has_more: value.has_more === true,
|
|
646
|
-
next_cursor: typeof value.next_cursor === "string" ? value.next_cursor : void 0
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
function normalizeCatalogueResult(payload) {
|
|
650
|
-
if (Array.isArray(payload)) {
|
|
651
|
-
return {
|
|
652
|
-
items: payload.map((product) => normalizeCatalogueProductPayload(product)),
|
|
653
|
-
is_complete: true
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
if (!isRecord(payload)) {
|
|
657
|
-
return {
|
|
658
|
-
items: [],
|
|
659
|
-
is_complete: true
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
const rawItems = Array.isArray(payload.products) ? payload.products : Array.isArray(payload.items) ? payload.items : [];
|
|
663
|
-
return {
|
|
664
|
-
items: rawItems.map((product) => normalizeCatalogueProductPayload(product)),
|
|
665
|
-
is_complete: typeof payload.is_complete === "boolean" ? payload.is_complete : true,
|
|
666
|
-
total_available: toFiniteNumber(payload.total_available),
|
|
667
|
-
pagination: normalizePagination(payload.pagination)
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
function normalizeCatalogueSnapshot(payload) {
|
|
671
|
-
if (Array.isArray(payload)) {
|
|
672
|
-
return {
|
|
673
|
-
categories: [],
|
|
674
|
-
products: payload.map((product) => normalizeCatalogueProductPayload(product)),
|
|
675
|
-
add_ons: [],
|
|
676
|
-
is_complete: true
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
if (!isRecord(payload)) {
|
|
680
|
-
return {
|
|
681
|
-
categories: [],
|
|
682
|
-
products: [],
|
|
683
|
-
add_ons: [],
|
|
684
|
-
is_complete: true
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
const rawProducts = Array.isArray(payload.products) ? payload.products : Array.isArray(payload.items) ? payload.items : [];
|
|
688
|
-
const rawCategories = Array.isArray(payload.categories) ? payload.categories : [];
|
|
689
|
-
const rawAddOns = Array.isArray(payload.add_ons) ? payload.add_ons : [];
|
|
690
|
-
return {
|
|
691
|
-
categories: rawCategories,
|
|
692
|
-
products: rawProducts.map((product) => normalizeCatalogueProductPayload(product)),
|
|
693
|
-
add_ons: rawAddOns.map((addOn) => normalizeAddOnPayload(addOn)),
|
|
694
|
-
is_complete: typeof payload.is_complete === "boolean" ? payload.is_complete : true,
|
|
695
|
-
total_available: toFiniteNumber(payload.total_available),
|
|
696
|
-
pagination: normalizePagination(payload.pagination)
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
var CatalogueQueries = class {
|
|
700
|
-
constructor(client) {
|
|
701
|
-
this.client = client;
|
|
702
|
-
}
|
|
703
|
-
async getCatalogue() {
|
|
704
|
-
const result = await safe(this.client.get("/api/v1/catalogue"));
|
|
705
|
-
if (!result.ok) return result;
|
|
706
|
-
return ok(normalizeCatalogueSnapshot(result.value));
|
|
707
|
-
}
|
|
708
|
-
async getProducts(options) {
|
|
709
|
-
const path = withQuery("/api/v1/catalogue/products", {
|
|
710
|
-
category_id: options?.category,
|
|
711
|
-
taxonomy_id: options?.taxonomy,
|
|
712
|
-
search: options?.search,
|
|
713
|
-
page: options?.page,
|
|
714
|
-
tags: options?.tags?.join(","),
|
|
715
|
-
featured: options?.featured,
|
|
716
|
-
in_stock: options?.in_stock,
|
|
717
|
-
min_price: options?.min_price,
|
|
718
|
-
max_price: options?.max_price,
|
|
719
|
-
sort_by: options?.sort_by,
|
|
720
|
-
sort_order: options?.sort_order,
|
|
721
|
-
limit: options?.limit,
|
|
722
|
-
offset: options?.offset,
|
|
723
|
-
cursor: options?.cursor
|
|
724
|
-
});
|
|
725
|
-
const result = await safe(this.client.get(path));
|
|
726
|
-
if (!result.ok) return result;
|
|
727
|
-
return ok(normalizeCatalogueResult(result.value));
|
|
728
|
-
}
|
|
729
|
-
async getProduct(id) {
|
|
730
|
-
const encodedId = encodeURIComponent(id);
|
|
731
|
-
const result = await safe(this.client.get(`/api/v1/catalogue/products/${encodedId}`));
|
|
732
|
-
if (!result.ok) return result;
|
|
733
|
-
return ok(normalizeCatalogueProductPayload(result.value));
|
|
734
|
-
}
|
|
735
|
-
async getProductBySlug(slug) {
|
|
736
|
-
const encodedSlug = encodeURIComponent(slug);
|
|
737
|
-
const result = await safe(this.client.get(`/api/v1/catalogue/products/slug/${encodedSlug}`));
|
|
738
|
-
if (!result.ok) return result;
|
|
739
|
-
return ok(normalizeCatalogueProductPayload(result.value));
|
|
740
|
-
}
|
|
741
|
-
async getVariants(productId) {
|
|
742
|
-
const encodedId = encodeURIComponent(productId);
|
|
743
|
-
return safe(this.client.get(`/api/v1/catalogue/products/${encodedId}/variants`));
|
|
744
|
-
}
|
|
745
|
-
async getVariantAxes(productId) {
|
|
746
|
-
const encodedId = encodeURIComponent(productId);
|
|
747
|
-
return safe(this.client.get(`/api/v1/catalogue/products/${encodedId}/variant-axes`));
|
|
748
|
-
}
|
|
749
|
-
async getVariantByAxisSelections(productId, selections) {
|
|
750
|
-
const encodedId = encodeURIComponent(productId);
|
|
751
|
-
return safe(
|
|
752
|
-
this.client.post(
|
|
753
|
-
`/api/v1/catalogue/products/${encodedId}/variants/find`,
|
|
754
|
-
{ axis_selections: selections }
|
|
755
|
-
)
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
async getVariantById(productId, variantId) {
|
|
759
|
-
const encodedProductId = encodeURIComponent(productId);
|
|
760
|
-
const encodedVariantId = encodeURIComponent(variantId);
|
|
761
|
-
return safe(
|
|
762
|
-
this.client.get(
|
|
763
|
-
`/api/v1/catalogue/products/${encodedProductId}/variants/${encodedVariantId}`
|
|
764
|
-
)
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
async getAddOns(productId) {
|
|
768
|
-
const encodedId = encodeURIComponent(productId);
|
|
769
|
-
return safe(this.client.get(`/api/v1/catalogue/products/${encodedId}/add-ons`));
|
|
770
|
-
}
|
|
771
|
-
async getCategories() {
|
|
772
|
-
return safe(this.client.get("/api/v1/catalogue/categories"));
|
|
773
|
-
}
|
|
774
|
-
async getCategory(id) {
|
|
775
|
-
const encodedId = encodeURIComponent(id);
|
|
776
|
-
return safe(this.client.get(`/api/v1/catalogue/categories/${encodedId}`));
|
|
777
|
-
}
|
|
778
|
-
async getCategoryBySlug(slug) {
|
|
779
|
-
const encodedSlug = encodeURIComponent(slug);
|
|
780
|
-
return safe(this.client.get(`/api/v1/catalogue/categories/slug/${encodedSlug}`));
|
|
781
|
-
}
|
|
782
|
-
async getCategoryProducts(categoryId) {
|
|
783
|
-
const encodedId = encodeURIComponent(categoryId);
|
|
784
|
-
return safe(this.client.get(`/api/v1/catalogue/categories/${encodedId}/products`));
|
|
785
|
-
}
|
|
786
|
-
async getCollections() {
|
|
787
|
-
return safe(this.client.get("/api/v1/catalogue/collections"));
|
|
788
|
-
}
|
|
789
|
-
async getCollection(id) {
|
|
790
|
-
const encodedId = encodeURIComponent(id);
|
|
791
|
-
return safe(this.client.get(`/api/v1/catalogue/collections/${encodedId}`));
|
|
792
|
-
}
|
|
793
|
-
async getCollectionBySlug(slug) {
|
|
794
|
-
const encodedSlug = encodeURIComponent(slug);
|
|
795
|
-
return safe(this.client.get(`/api/v1/catalogue/collections/slug/${encodedSlug}`));
|
|
796
|
-
}
|
|
797
|
-
async getCollectionProducts(collectionId) {
|
|
798
|
-
const encodedId = encodeURIComponent(collectionId);
|
|
799
|
-
return safe(this.client.get(`/api/v1/catalogue/collections/${encodedId}/products`));
|
|
800
|
-
}
|
|
801
|
-
async searchCollections(query, limit = 20) {
|
|
802
|
-
const path = withQuery("/api/v1/catalogue/collections", { search: query, limit });
|
|
803
|
-
return safe(this.client.get(path));
|
|
804
|
-
}
|
|
805
|
-
async getBundles() {
|
|
806
|
-
return safe(this.client.get("/api/v1/catalogue/bundles"));
|
|
807
|
-
}
|
|
808
|
-
async searchBundles(query, limit = 20) {
|
|
809
|
-
const path = withQuery("/api/v1/catalogue/bundles", { search: query, limit });
|
|
810
|
-
return safe(this.client.get(path));
|
|
811
|
-
}
|
|
812
|
-
async getComposites(options) {
|
|
813
|
-
const path = withQuery("/api/v1/catalogue/composites", { limit: options?.limit });
|
|
814
|
-
return safe(this.client.get(path));
|
|
815
|
-
}
|
|
816
|
-
async calculateCompositePrice(compositeId, selections, locationId) {
|
|
817
|
-
const encodedId = encodeURIComponent(compositeId);
|
|
818
|
-
return safe(
|
|
819
|
-
this.client.post(
|
|
820
|
-
`/api/v1/catalogue/composites/${encodedId}/calculate-price`,
|
|
821
|
-
{
|
|
822
|
-
selections,
|
|
823
|
-
location_id: locationId
|
|
824
|
-
}
|
|
825
|
-
)
|
|
826
|
-
);
|
|
827
|
-
}
|
|
828
|
-
async fetchQuote(input) {
|
|
829
|
-
return safe(this.client.post("/api/v1/catalogue/quotes", input));
|
|
830
|
-
}
|
|
831
|
-
async getQuote(quoteId) {
|
|
832
|
-
const encodedQuoteId = encodeURIComponent(quoteId);
|
|
833
|
-
return safe(this.client.get(`/api/v1/catalogue/quotes/${encodedQuoteId}`));
|
|
834
|
-
}
|
|
835
|
-
async refreshQuote(input) {
|
|
836
|
-
const encodedQuoteId = encodeURIComponent(input.quote_id);
|
|
837
|
-
return safe(
|
|
838
|
-
this.client.post(
|
|
839
|
-
`/api/v1/catalogue/quotes/${encodedQuoteId}/refresh`,
|
|
840
|
-
input
|
|
841
|
-
)
|
|
842
|
-
);
|
|
843
|
-
}
|
|
844
|
-
async getTaxonomies(parentId) {
|
|
845
|
-
const path = withQuery("/api/v1/catalogue/taxonomies", { parent_id: parentId });
|
|
846
|
-
return safe(this.client.get(path));
|
|
847
|
-
}
|
|
848
|
-
async getTaxonomy(id) {
|
|
849
|
-
const encodedId = encodeURIComponent(id);
|
|
850
|
-
return safe(this.client.get(`/api/v1/catalogue/taxonomies/${encodedId}`));
|
|
851
|
-
}
|
|
852
|
-
async getTaxonomyPath(id) {
|
|
853
|
-
const encodedId = encodeURIComponent(id);
|
|
854
|
-
return safe(this.client.get(`/api/v1/catalogue/taxonomies/${encodedId}/path`));
|
|
855
|
-
}
|
|
856
|
-
async searchTaxonomies(query, limit) {
|
|
857
|
-
const path = withQuery("/api/v1/catalogue/taxonomies/search", { q: query, limit });
|
|
858
|
-
return safe(this.client.get(path));
|
|
859
|
-
}
|
|
860
|
-
async getProductSchedules(productId) {
|
|
861
|
-
const encodedId = encodeURIComponent(productId);
|
|
862
|
-
return safe(this.client.get(`/api/v1/catalogue/products/${encodedId}/schedules`));
|
|
863
|
-
}
|
|
864
|
-
async checkProductAvailableNow(productId, locationId) {
|
|
865
|
-
const encodedId = encodeURIComponent(productId);
|
|
866
|
-
const path = withQuery(`/api/v1/catalogue/products/${encodedId}/available-now`, { location_id: locationId });
|
|
867
|
-
return safe(this.client.get(path));
|
|
868
|
-
}
|
|
869
|
-
async getDeals(locationId) {
|
|
870
|
-
const path = withQuery("/api/v1/catalogue/deals", { location_id: locationId });
|
|
871
|
-
return safe(this.client.get(path));
|
|
872
|
-
}
|
|
873
|
-
async getProductsOnSale() {
|
|
874
|
-
return safe(this.client.get("/api/v1/catalogue/deals/on-sale"));
|
|
875
|
-
}
|
|
876
|
-
async getProductDeals(productId) {
|
|
877
|
-
const encodedId = encodeURIComponent(productId);
|
|
878
|
-
return safe(this.client.get(`/api/v1/catalogue/products/${encodedId}/deals`));
|
|
879
|
-
}
|
|
880
|
-
async getCategoryDeals(categoryId) {
|
|
881
|
-
const encodedId = encodeURIComponent(categoryId);
|
|
882
|
-
return safe(this.client.get(`/api/v1/catalogue/categories/${encodedId}/deals`));
|
|
883
|
-
}
|
|
884
|
-
async getCollectionDeals(collectionId) {
|
|
885
|
-
const encodedId = encodeURIComponent(collectionId);
|
|
886
|
-
return safe(this.client.get(`/api/v1/catalogue/collections/${encodedId}/deals`));
|
|
887
|
-
}
|
|
888
|
-
async validateDiscountCode(code, orderSubtotal, locationId) {
|
|
889
|
-
return safe(
|
|
890
|
-
this.client.post("/api/v1/catalogue/deals/validate", {
|
|
891
|
-
discount_code: code,
|
|
892
|
-
order_subtotal: orderSubtotal,
|
|
893
|
-
location_id: locationId
|
|
894
|
-
})
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
async search(query, options) {
|
|
898
|
-
const result = await this.getProducts({
|
|
899
|
-
search: query,
|
|
900
|
-
category: options?.category,
|
|
901
|
-
limit: options?.limit ?? 20
|
|
902
|
-
});
|
|
903
|
-
if (!result.ok) return result;
|
|
904
|
-
return ok(result.value.items);
|
|
905
|
-
}
|
|
906
|
-
async searchProducts(query, options) {
|
|
907
|
-
return this.search(query, options);
|
|
908
|
-
}
|
|
909
|
-
async getMenu(options) {
|
|
910
|
-
const path = withQuery("/api/v1/catalogue/menu", {
|
|
911
|
-
category_id: options?.category,
|
|
912
|
-
limit: options?.limit
|
|
913
|
-
});
|
|
914
|
-
return safe(this.client.get(path));
|
|
915
|
-
}
|
|
916
|
-
async getMenuCategory(categoryId) {
|
|
917
|
-
const encodedId = encodeURIComponent(categoryId);
|
|
918
|
-
return safe(this.client.get(`/api/v1/catalogue/menu/categories/${encodedId}`));
|
|
919
|
-
}
|
|
920
|
-
async getMenuItem(itemId) {
|
|
921
|
-
const encodedId = encodeURIComponent(itemId);
|
|
922
|
-
return safe(this.client.get(`/api/v1/catalogue/menu/items/${encodedId}`));
|
|
923
|
-
}
|
|
924
|
-
async getTags(options) {
|
|
925
|
-
const path = withQuery("/api/v1/catalogue/tags", {
|
|
926
|
-
tag_group: options?.tag_group,
|
|
927
|
-
search: options?.search,
|
|
928
|
-
min_usage: options?.min_usage,
|
|
929
|
-
limit: options?.limit,
|
|
930
|
-
offset: options?.offset
|
|
931
|
-
});
|
|
932
|
-
return safe(this.client.get(path));
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
|
|
936
|
-
// src/cart.ts
|
|
937
|
-
function isUICartResponse(value) {
|
|
938
|
-
return "cart" in value;
|
|
939
|
-
}
|
|
940
|
-
function unwrapEnrichedCart(value) {
|
|
941
|
-
return isUICartResponse(value) ? value.cart : value;
|
|
942
|
-
}
|
|
943
|
-
var CartOperations = class {
|
|
944
|
-
constructor(client) {
|
|
945
|
-
this.client = client;
|
|
946
|
-
}
|
|
947
|
-
async get() {
|
|
948
|
-
const result = await safe(this.client.get("/api/v1/cart"));
|
|
949
|
-
if (!result.ok) return result;
|
|
950
|
-
return ok(unwrapEnrichedCart(result.value));
|
|
951
|
-
}
|
|
952
|
-
async getRaw() {
|
|
953
|
-
return safe(this.client.get("/api/v1/cart"));
|
|
954
|
-
}
|
|
955
|
-
async getItems() {
|
|
956
|
-
return safe(this.client.get("/api/v1/cart/items"));
|
|
957
|
-
}
|
|
958
|
-
async getCount() {
|
|
959
|
-
return safe(this.client.get("/api/v1/cart/count"));
|
|
960
|
-
}
|
|
961
|
-
async getTotal() {
|
|
962
|
-
return safe(this.client.get("/api/v1/cart/total"));
|
|
963
|
-
}
|
|
964
|
-
async getSummary() {
|
|
965
|
-
const cartResult = await this.get();
|
|
966
|
-
if (!cartResult.ok) return cartResult;
|
|
967
|
-
const cart = cartResult.value;
|
|
968
|
-
return ok({
|
|
969
|
-
item_count: cart.items.length,
|
|
970
|
-
total_items: cart.items.reduce((sum, item) => sum + item.quantity, 0),
|
|
971
|
-
subtotal: cart.pricing.subtotal,
|
|
972
|
-
discount_amount: cart.pricing.total_discounts,
|
|
973
|
-
tax_amount: cart.pricing.tax_amount,
|
|
974
|
-
total: cart.pricing.total_price,
|
|
975
|
-
currency: cart.pricing.currency
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
async addItem(input) {
|
|
979
|
-
return safe(this.client.post("/api/v1/cart/items", input));
|
|
980
|
-
}
|
|
981
|
-
async updateItem(cartItemId, updates) {
|
|
982
|
-
if (typeof updates.quantity === "number") {
|
|
983
|
-
return this.updateQuantity(cartItemId, updates.quantity);
|
|
984
|
-
}
|
|
985
|
-
const encodedId = encodeURIComponent(cartItemId);
|
|
986
|
-
return safe(this.client.patch(`/api/v1/cart/items/${encodedId}`, updates));
|
|
987
|
-
}
|
|
988
|
-
async updateQuantity(cartItemId, quantity) {
|
|
989
|
-
const encodedId = encodeURIComponent(cartItemId);
|
|
990
|
-
return safe(
|
|
991
|
-
this.client.patch(`/api/v1/cart/items/${encodedId}`, {
|
|
992
|
-
quantity
|
|
993
|
-
})
|
|
994
|
-
);
|
|
995
|
-
}
|
|
996
|
-
async removeItem(cartItemId) {
|
|
997
|
-
const encodedId = encodeURIComponent(cartItemId);
|
|
998
|
-
return safe(this.client.delete(`/api/v1/cart/items/${encodedId}`));
|
|
999
|
-
}
|
|
1000
|
-
async clear() {
|
|
1001
|
-
return safe(this.client.delete("/api/v1/cart"));
|
|
1002
|
-
}
|
|
1003
|
-
async applyCoupon(code) {
|
|
1004
|
-
return safe(
|
|
1005
|
-
this.client.post("/api/v1/cart/coupons", {
|
|
1006
|
-
coupon_code: code
|
|
1007
|
-
})
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
async removeCoupon() {
|
|
1011
|
-
return safe(this.client.delete("/api/v1/cart/coupons/current"));
|
|
1012
|
-
}
|
|
1013
|
-
async isEmpty() {
|
|
1014
|
-
const countResult = await this.getCount();
|
|
1015
|
-
if (!countResult.ok) return countResult;
|
|
1016
|
-
return ok(countResult.value === 0);
|
|
1017
|
-
}
|
|
1018
|
-
async hasItem(productId, variantId) {
|
|
1019
|
-
const itemsResult = await this.getItems();
|
|
1020
|
-
if (!itemsResult.ok) return itemsResult;
|
|
1021
|
-
const found = itemsResult.value.some((item) => {
|
|
1022
|
-
const matchesProduct = item.item_id === productId;
|
|
1023
|
-
if (!variantId) return matchesProduct;
|
|
1024
|
-
const config = item.configuration;
|
|
1025
|
-
if ("variant" in config && config.variant) {
|
|
1026
|
-
return matchesProduct && config.variant.variant_id === variantId;
|
|
1027
|
-
}
|
|
1028
|
-
return matchesProduct;
|
|
1029
|
-
});
|
|
1030
|
-
return ok(found);
|
|
1031
|
-
}
|
|
1032
|
-
async findItem(productId, variantId) {
|
|
1033
|
-
const itemsResult = await this.getItems();
|
|
1034
|
-
if (!itemsResult.ok) return itemsResult;
|
|
1035
|
-
const found = itemsResult.value.find((item) => {
|
|
1036
|
-
const matchesProduct = item.item_id === productId;
|
|
1037
|
-
if (!variantId) return matchesProduct;
|
|
1038
|
-
const config = item.configuration;
|
|
1039
|
-
if ("variant" in config && config.variant) {
|
|
1040
|
-
return matchesProduct && config.variant.variant_id === variantId;
|
|
1041
|
-
}
|
|
1042
|
-
return matchesProduct;
|
|
1043
|
-
});
|
|
1044
|
-
return ok(found);
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
// src/constants.ts
|
|
1049
|
-
var MOBILE_MONEY_PROVIDER = {
|
|
1050
|
-
MTN: "mtn",
|
|
1051
|
-
VODAFONE: "vodafone",
|
|
1052
|
-
AIRTEL: "airtel"
|
|
1053
|
-
};
|
|
1054
|
-
|
|
1055
|
-
// src/utils/price.ts
|
|
1056
|
-
var CURRENCY_SYMBOLS = {
|
|
1057
|
-
USD: "$",
|
|
1058
|
-
EUR: "\u20AC",
|
|
1059
|
-
GBP: "\xA3",
|
|
1060
|
-
JPY: "\xA5",
|
|
1061
|
-
CNY: "\xA5",
|
|
1062
|
-
CHF: "CHF",
|
|
1063
|
-
CAD: "C$",
|
|
1064
|
-
AUD: "A$",
|
|
1065
|
-
NZD: "NZ$",
|
|
1066
|
-
HKD: "HK$",
|
|
1067
|
-
SGD: "S$",
|
|
1068
|
-
INR: "\u20B9",
|
|
1069
|
-
BRL: "R$",
|
|
1070
|
-
MXN: "MX$",
|
|
1071
|
-
KRW: "\u20A9",
|
|
1072
|
-
RUB: "\u20BD",
|
|
1073
|
-
TRY: "\u20BA",
|
|
1074
|
-
THB: "\u0E3F",
|
|
1075
|
-
PLN: "z\u0142",
|
|
1076
|
-
SEK: "kr",
|
|
1077
|
-
NOK: "kr",
|
|
1078
|
-
DKK: "kr",
|
|
1079
|
-
CZK: "K\u010D",
|
|
1080
|
-
HUF: "Ft",
|
|
1081
|
-
ILS: "\u20AA",
|
|
1082
|
-
AED: "\u062F.\u0625",
|
|
1083
|
-
SAR: "\uFDFC",
|
|
1084
|
-
MYR: "RM",
|
|
1085
|
-
PHP: "\u20B1",
|
|
1086
|
-
IDR: "Rp",
|
|
1087
|
-
VND: "\u20AB",
|
|
1088
|
-
TWD: "NT$",
|
|
1089
|
-
GHS: "GH\u20B5",
|
|
1090
|
-
NGN: "\u20A6",
|
|
1091
|
-
KES: "KSh",
|
|
1092
|
-
ZAR: "R",
|
|
1093
|
-
XOF: "CFA",
|
|
1094
|
-
XAF: "FCFA",
|
|
1095
|
-
EGP: "E\xA3",
|
|
1096
|
-
MAD: "MAD",
|
|
1097
|
-
TZS: "TSh",
|
|
1098
|
-
UGX: "USh",
|
|
1099
|
-
RWF: "FRw",
|
|
1100
|
-
ETB: "Br",
|
|
1101
|
-
ZMW: "ZK",
|
|
1102
|
-
BWP: "P",
|
|
1103
|
-
MUR: "\u20A8",
|
|
1104
|
-
SCR: "\u20A8",
|
|
1105
|
-
NAD: "N$",
|
|
1106
|
-
SZL: "E",
|
|
1107
|
-
LSL: "L",
|
|
1108
|
-
MWK: "MK",
|
|
1109
|
-
AOA: "Kz",
|
|
1110
|
-
CDF: "FC",
|
|
1111
|
-
GMD: "D",
|
|
1112
|
-
GNF: "FG",
|
|
1113
|
-
LRD: "L$",
|
|
1114
|
-
SLL: "Le",
|
|
1115
|
-
MZN: "MT",
|
|
1116
|
-
SDG: "SDG",
|
|
1117
|
-
SSP: "SSP",
|
|
1118
|
-
SOS: "Sh.So.",
|
|
1119
|
-
DJF: "Fdj",
|
|
1120
|
-
ERN: "Nfk",
|
|
1121
|
-
CVE: "$",
|
|
1122
|
-
STN: "Db",
|
|
1123
|
-
KMF: "CF",
|
|
1124
|
-
BIF: "FBu"
|
|
1125
|
-
};
|
|
1126
|
-
function getCurrencySymbol(currencyCode3) {
|
|
1127
|
-
return CURRENCY_SYMBOLS[currencyCode3.toUpperCase()] || currencyCode3;
|
|
1128
|
-
}
|
|
1129
|
-
function formatPrice(amount, currency = "GHS", locale = "en-US") {
|
|
1130
|
-
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
1131
|
-
if (isNaN(numAmount)) {
|
|
1132
|
-
return `${getCurrencySymbol(currency)}0.00`;
|
|
1133
|
-
}
|
|
1134
|
-
try {
|
|
1135
|
-
return new Intl.NumberFormat(locale, {
|
|
1136
|
-
style: "currency",
|
|
1137
|
-
currency: currency.toUpperCase(),
|
|
1138
|
-
minimumFractionDigits: 2,
|
|
1139
|
-
maximumFractionDigits: 2
|
|
1140
|
-
}).format(numAmount);
|
|
1141
|
-
} catch {
|
|
1142
|
-
return `${getCurrencySymbol(currency)}${numAmount.toFixed(2)}`;
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
function parsePrice(value) {
|
|
1146
|
-
if (value === void 0 || value === null) {
|
|
1147
|
-
return 0;
|
|
1148
|
-
}
|
|
1149
|
-
if (typeof value === "number") {
|
|
1150
|
-
return isNaN(value) ? 0 : value;
|
|
1151
|
-
}
|
|
1152
|
-
const cleaned = value.replace(/[^\d.-]/g, "");
|
|
1153
|
-
const parsed = parseFloat(cleaned);
|
|
1154
|
-
return isNaN(parsed) ? 0 : parsed;
|
|
1155
|
-
}
|
|
1156
|
-
function getDisplayPrice(product) {
|
|
1157
|
-
if (product.price_info) {
|
|
1158
|
-
return parsePrice(product.price_info.final_price);
|
|
1159
|
-
}
|
|
1160
|
-
if (product.final_price !== void 0 && product.final_price !== null) {
|
|
1161
|
-
return parsePrice(product.final_price);
|
|
1162
|
-
}
|
|
1163
|
-
if (product.default_price !== void 0 && product.default_price !== null) {
|
|
1164
|
-
return parsePrice(product.default_price);
|
|
1165
|
-
}
|
|
1166
|
-
return 0;
|
|
1167
|
-
}
|
|
1168
|
-
function getBasePrice(product) {
|
|
1169
|
-
if (product.price_info) {
|
|
1170
|
-
return parsePrice(product.price_info.base_price);
|
|
1171
|
-
}
|
|
1172
|
-
if (product.base_price !== void 0 && product.base_price !== null) {
|
|
1173
|
-
return parsePrice(product.base_price);
|
|
1174
|
-
}
|
|
1175
|
-
if (product.default_price !== void 0 && product.default_price !== null) {
|
|
1176
|
-
return parsePrice(product.default_price);
|
|
1177
|
-
}
|
|
1178
|
-
return 0;
|
|
1179
|
-
}
|
|
1180
|
-
function isOnSale(product) {
|
|
1181
|
-
const basePrice = getBasePrice(product);
|
|
1182
|
-
const finalPrice = getDisplayPrice(product);
|
|
1183
|
-
return basePrice > finalPrice && basePrice > 0;
|
|
1184
|
-
}
|
|
1185
|
-
function getDiscountPercentage(product) {
|
|
1186
|
-
const basePrice = getBasePrice(product);
|
|
1187
|
-
const finalPrice = getDisplayPrice(product);
|
|
1188
|
-
if (basePrice > finalPrice && basePrice > 0) {
|
|
1189
|
-
return Math.round((basePrice - finalPrice) / basePrice * 100);
|
|
1190
|
-
}
|
|
1191
|
-
return 0;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// src/utils/payment.ts
|
|
1195
|
-
var PAYMENT_SUCCESS_STATUSES = /* @__PURE__ */ new Set([
|
|
1196
|
-
"success",
|
|
1197
|
-
"succeeded",
|
|
1198
|
-
"paid",
|
|
1199
|
-
"captured",
|
|
1200
|
-
"completed",
|
|
1201
|
-
"authorized"
|
|
1202
|
-
]);
|
|
1203
|
-
var PAYMENT_FAILURE_STATUSES = /* @__PURE__ */ new Set([
|
|
1204
|
-
"failed",
|
|
1205
|
-
"declined",
|
|
1206
|
-
"cancelled",
|
|
1207
|
-
"voided",
|
|
1208
|
-
"error",
|
|
1209
|
-
"abandoned"
|
|
1210
|
-
]);
|
|
1211
|
-
var PAYMENT_REQUIRES_ACTION_STATUSES = /* @__PURE__ */ new Set([
|
|
1212
|
-
"requires_action",
|
|
1213
|
-
"requires_payment_method",
|
|
1214
|
-
"requires_capture"
|
|
1215
|
-
]);
|
|
1216
|
-
var PAYMENT_STATUS_ALIAS_MAP = {
|
|
1217
|
-
ok: "success",
|
|
1218
|
-
done: "success",
|
|
1219
|
-
paid: "paid",
|
|
1220
|
-
paid_in_full: "paid",
|
|
1221
|
-
paid_successfully: "paid",
|
|
1222
|
-
succeeded: "success",
|
|
1223
|
-
captured: "captured",
|
|
1224
|
-
completed: "completed",
|
|
1225
|
-
pending_confirmation: "pending_confirmation",
|
|
1226
|
-
requires_authorization: "requires_action",
|
|
1227
|
-
requires_action: "requires_action",
|
|
1228
|
-
requires_payment_method: "requires_payment_method",
|
|
1229
|
-
requires_capture: "requires_capture",
|
|
1230
|
-
partially_paid: "partially_paid",
|
|
1231
|
-
partially_refunded: "partially_refunded",
|
|
1232
|
-
card_declined: "declined",
|
|
1233
|
-
canceled: "cancelled",
|
|
1234
|
-
authorized: "authorized",
|
|
1235
|
-
cancelled: "cancelled",
|
|
1236
|
-
unresolved: "pending"
|
|
1237
|
-
};
|
|
1238
|
-
var KNOWN_PAYMENT_STATUSES = /* @__PURE__ */ new Set([
|
|
1239
|
-
"initialized",
|
|
1240
|
-
"pending",
|
|
1241
|
-
"processing",
|
|
1242
|
-
"authorized",
|
|
1243
|
-
"captured",
|
|
1244
|
-
"failed",
|
|
1245
|
-
"cancelled",
|
|
1246
|
-
"refunded",
|
|
1247
|
-
"unknown",
|
|
1248
|
-
"voided",
|
|
1249
|
-
"requires_action",
|
|
1250
|
-
"abandoned",
|
|
1251
|
-
"reversed",
|
|
1252
|
-
"disputed",
|
|
1253
|
-
"refund_pending",
|
|
1254
|
-
"created",
|
|
1255
|
-
"pending_confirmation",
|
|
1256
|
-
"success",
|
|
1257
|
-
"succeeded",
|
|
1258
|
-
"declined",
|
|
1259
|
-
"partially_refunded",
|
|
1260
|
-
"partially_paid",
|
|
1261
|
-
"paid",
|
|
1262
|
-
"unpaid",
|
|
1263
|
-
"requires_payment_method",
|
|
1264
|
-
"requires_capture",
|
|
1265
|
-
"completed",
|
|
1266
|
-
"error"
|
|
1267
|
-
]);
|
|
1268
|
-
function normalizeStatusToken(status) {
|
|
1269
|
-
return status?.trim().toLowerCase().replace(/[\s-]+/g, "_") ?? "";
|
|
1270
|
-
}
|
|
1271
|
-
function normalizePaymentStatusValue(status) {
|
|
1272
|
-
const normalized = normalizeStatusToken(status);
|
|
1273
|
-
if (Object.prototype.hasOwnProperty.call(PAYMENT_STATUS_ALIAS_MAP, normalized)) {
|
|
1274
|
-
return PAYMENT_STATUS_ALIAS_MAP[normalized];
|
|
1275
|
-
}
|
|
1276
|
-
return KNOWN_PAYMENT_STATUSES.has(normalized) ? normalized : "unknown";
|
|
1277
|
-
}
|
|
1278
|
-
function isPaymentStatusSuccess(status) {
|
|
1279
|
-
const normalizedStatus = normalizePaymentStatusValue(status);
|
|
1280
|
-
return PAYMENT_SUCCESS_STATUSES.has(normalizedStatus);
|
|
1281
|
-
}
|
|
1282
|
-
function isPaymentStatusFailure(status) {
|
|
1283
|
-
const normalizedStatus = normalizePaymentStatusValue(status);
|
|
1284
|
-
return PAYMENT_FAILURE_STATUSES.has(normalizedStatus);
|
|
1285
|
-
}
|
|
1286
|
-
function isPaymentStatusRequiresAction(status) {
|
|
1287
|
-
const normalizedStatus = normalizePaymentStatusValue(status);
|
|
1288
|
-
return PAYMENT_REQUIRES_ACTION_STATUSES.has(normalizedStatus);
|
|
1289
|
-
}
|
|
1290
|
-
function normalizeStatusResponse(response) {
|
|
1291
|
-
if (!response || typeof response !== "object") {
|
|
1292
|
-
return {
|
|
1293
|
-
status: "pending",
|
|
1294
|
-
paid: false,
|
|
1295
|
-
amount: moneyFromNumber(0),
|
|
1296
|
-
currency: "",
|
|
1297
|
-
message: "No status available"
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
const res = response;
|
|
1301
|
-
const normalizedStatus = normalizePaymentStatusValue(res.status ?? void 0);
|
|
1302
|
-
const normalizedAmount = typeof res.amount === "string" ? money(res.amount) : typeof res.amount === "number" && Number.isFinite(res.amount) ? moneyFromNumber(res.amount) : moneyFromNumber(0);
|
|
1303
|
-
const normalizedCurrency = typeof res.currency === "string" && res.currency.trim().length > 0 ? res.currency : "";
|
|
1304
|
-
const paidValue = res.paid === true;
|
|
1305
|
-
const derivedPaid = paidValue || [
|
|
1306
|
-
"success",
|
|
1307
|
-
"succeeded",
|
|
1308
|
-
"paid",
|
|
1309
|
-
"captured",
|
|
1310
|
-
"authorized",
|
|
1311
|
-
"completed"
|
|
1312
|
-
].includes(normalizedStatus);
|
|
1313
|
-
return {
|
|
1314
|
-
status: normalizedStatus,
|
|
1315
|
-
paid: derivedPaid,
|
|
1316
|
-
amount: normalizedAmount,
|
|
1317
|
-
currency: normalizedCurrency,
|
|
1318
|
-
reference: res.reference,
|
|
1319
|
-
message: res.message || ""
|
|
1320
|
-
};
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
// src/utils/paystack.ts
|
|
1324
|
-
async function openPaystackPopup(options, signal) {
|
|
1325
|
-
if (typeof window === "undefined") {
|
|
1326
|
-
return { success: false, error: "PROVIDER_UNAVAILABLE" };
|
|
1327
|
-
}
|
|
1328
|
-
let PaystackPop;
|
|
1329
|
-
try {
|
|
1330
|
-
const imported = await import('@paystack/inline-js');
|
|
1331
|
-
PaystackPop = imported.default;
|
|
1332
|
-
} catch {
|
|
1333
|
-
return { success: false, error: "PROVIDER_UNAVAILABLE" };
|
|
1334
|
-
}
|
|
1335
|
-
return new Promise((resolve) => {
|
|
1336
|
-
let settled = false;
|
|
1337
|
-
const resolveOnce = (result) => {
|
|
1338
|
-
if (settled) {
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
|
-
settled = true;
|
|
1342
|
-
if (signal) {
|
|
1343
|
-
signal.removeEventListener("abort", onAbort);
|
|
1344
|
-
}
|
|
1345
|
-
resolve(result);
|
|
1346
|
-
};
|
|
1347
|
-
const onAbort = () => {
|
|
1348
|
-
resolveOnce({ success: false, error: "CANCELLED" });
|
|
1349
|
-
};
|
|
1350
|
-
if (signal?.aborted) {
|
|
1351
|
-
resolveOnce({ success: false, error: "CANCELLED" });
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
if (signal) {
|
|
1355
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1356
|
-
}
|
|
1357
|
-
try {
|
|
1358
|
-
const popup = new PaystackPop();
|
|
1359
|
-
popup.newTransaction({
|
|
1360
|
-
key: options.key,
|
|
1361
|
-
email: options.email,
|
|
1362
|
-
amount: options.amount,
|
|
1363
|
-
currency: options.currency,
|
|
1364
|
-
reference: options.reference,
|
|
1365
|
-
accessCode: options.accessCode,
|
|
1366
|
-
onSuccess: (transaction) => {
|
|
1367
|
-
resolveOnce({
|
|
1368
|
-
success: true,
|
|
1369
|
-
reference: transaction.reference ?? options.reference
|
|
1370
|
-
});
|
|
1371
|
-
},
|
|
1372
|
-
onCancel: () => {
|
|
1373
|
-
resolveOnce({ success: false, error: "PAYMENT_CANCELLED" });
|
|
1374
|
-
},
|
|
1375
|
-
onError: (error) => {
|
|
1376
|
-
resolveOnce({
|
|
1377
|
-
success: false,
|
|
1378
|
-
error: error?.message || "PAYMENT_FAILED"
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
});
|
|
1382
|
-
} catch {
|
|
1383
|
-
resolveOnce({ success: false, error: "POPUP_BLOCKED" });
|
|
1384
|
-
}
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// src/utils/checkout-resolver.ts
|
|
1389
|
-
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
1390
|
-
var DEFAULT_MAX_POLL_ATTEMPTS = 60;
|
|
1391
|
-
var MAX_CONSECUTIVE_NETWORK_ERRORS = 5;
|
|
1392
|
-
var CARD_PROVIDER_PAYSTACK = "paystack";
|
|
1393
|
-
var NEXT_ACTION_NONE = "none";
|
|
1394
|
-
var NEXT_ACTION_CARD_POPUP = "card_popup";
|
|
1395
|
-
var NEXT_ACTION_REDIRECT = "redirect";
|
|
1396
|
-
var NEXT_ACTION_AUTHORIZATION = "authorization";
|
|
1397
|
-
function normalizeCardPopupError(error) {
|
|
1398
|
-
if (!error || error === "PAYMENT_CANCELLED" || error === "CANCELLED") {
|
|
1399
|
-
return "PAYMENT_CANCELLED";
|
|
1400
|
-
}
|
|
1401
|
-
if (error === "PROVIDER_UNAVAILABLE") {
|
|
1402
|
-
return "PROVIDER_UNAVAILABLE";
|
|
1403
|
-
}
|
|
1404
|
-
if (error === "POPUP_BLOCKED") {
|
|
1405
|
-
return "POPUP_BLOCKED";
|
|
1406
|
-
}
|
|
1407
|
-
return "PAYMENT_FAILED";
|
|
1408
|
-
}
|
|
1409
|
-
function normalizeCardProvider(value) {
|
|
1410
|
-
const normalized = value?.trim().toLowerCase();
|
|
1411
|
-
return normalized && normalized.length > 0 ? normalized : CARD_PROVIDER_PAYSTACK;
|
|
1412
|
-
}
|
|
1413
|
-
async function openCardPopup(provider, checkoutResult, email, currency, signal) {
|
|
1414
|
-
if (typeof window === "undefined") {
|
|
1415
|
-
return { success: false, error: "PROVIDER_UNAVAILABLE" };
|
|
1416
|
-
}
|
|
1417
|
-
if (provider === CARD_PROVIDER_PAYSTACK) {
|
|
1418
|
-
return openPaystackPopup(
|
|
1419
|
-
{
|
|
1420
|
-
key: checkoutResult.public_key || "",
|
|
1421
|
-
email,
|
|
1422
|
-
accessCode: checkoutResult.client_secret,
|
|
1423
|
-
reference: checkoutResult.payment_reference || checkoutResult.client_secret,
|
|
1424
|
-
currency
|
|
1425
|
-
},
|
|
1426
|
-
signal
|
|
1427
|
-
);
|
|
1428
|
-
}
|
|
1429
|
-
return { success: false, error: "PROVIDER_UNAVAILABLE" };
|
|
1430
|
-
}
|
|
1431
|
-
var VALID_MOBILE_MONEY_PROVIDERS = new Set(Object.values(MOBILE_MONEY_PROVIDER));
|
|
1432
|
-
function isValidMobileMoneyProvider(value) {
|
|
1433
|
-
return VALID_MOBILE_MONEY_PROVIDERS.has(value);
|
|
1434
|
-
}
|
|
1435
|
-
function normalizeAuthorizationType(value) {
|
|
1436
|
-
if (typeof value !== "string") return void 0;
|
|
1437
|
-
const lower = value.toLowerCase();
|
|
1438
|
-
return lower === "otp" || lower === "pin" ? lower : void 0;
|
|
1439
|
-
}
|
|
1440
|
-
function formatMoney2(value) {
|
|
1441
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1442
|
-
return value.toFixed(2);
|
|
1443
|
-
}
|
|
1444
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
1445
|
-
return value;
|
|
1446
|
-
}
|
|
1447
|
-
return "0.00";
|
|
1448
|
-
}
|
|
1449
|
-
function createAbortError() {
|
|
1450
|
-
const error = new Error("CANCELLED");
|
|
1451
|
-
error.name = "AbortError";
|
|
1452
|
-
return error;
|
|
1453
|
-
}
|
|
1454
|
-
function isAbortError(error) {
|
|
1455
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
1456
|
-
return true;
|
|
1457
|
-
}
|
|
1458
|
-
return error instanceof Error && (error.name === "AbortError" || error.message === "CANCELLED");
|
|
1459
|
-
}
|
|
1460
|
-
var CheckoutResolver = class {
|
|
1461
|
-
constructor(options) {
|
|
1462
|
-
this.client = options.client;
|
|
1463
|
-
this.checkoutData = options.checkoutData;
|
|
1464
|
-
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1465
|
-
this.maxPollAttempts = options.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
|
|
1466
|
-
this.onStatusChange = options.onStatusChange;
|
|
1467
|
-
this.onAuthorizationRequired = options.onAuthorizationRequired;
|
|
1468
|
-
this.signal = options.signal;
|
|
1469
|
-
this.returnUrl = options.returnUrl;
|
|
1470
|
-
this.enrollInLink = options.enrollInLink !== false;
|
|
1471
|
-
this.businessId = options.businessId;
|
|
1472
|
-
this.addressData = options.addressData;
|
|
1473
|
-
this.paymentData = options.paymentData;
|
|
1474
|
-
this.orderType = options.orderType;
|
|
1475
|
-
this.cartTotal = options.cartTotal;
|
|
1476
|
-
this.cartCurrency = options.cartCurrency;
|
|
1477
|
-
this.allowPopups = options.allowPopups ?? typeof window !== "undefined";
|
|
1478
|
-
}
|
|
1479
|
-
async resolve(checkoutResult) {
|
|
1480
|
-
try {
|
|
1481
|
-
this.ensureNotAborted();
|
|
1482
|
-
if (!checkoutResult.order_id) {
|
|
1483
|
-
return this.fail("CHECKOUT_FAILED", "Checkout did not return an order ID.", false);
|
|
1484
|
-
}
|
|
1485
|
-
if (checkoutResult.next_action) {
|
|
1486
|
-
return this.resolveNextAction(checkoutResult);
|
|
1487
|
-
}
|
|
1488
|
-
let latestCheckoutResult = checkoutResult;
|
|
1489
|
-
let authorizationType = normalizeAuthorizationType(checkoutResult.authorization_type);
|
|
1490
|
-
let paymentReference = checkoutResult.payment_reference;
|
|
1491
|
-
if (this.isSuccessfulStatus(checkoutResult.payment_status)) {
|
|
1492
|
-
return this.finalizeSuccess(checkoutResult);
|
|
1493
|
-
}
|
|
1494
|
-
if (checkoutResult.client_secret && checkoutResult.public_key) {
|
|
1495
|
-
const provider = normalizeCardProvider(checkoutResult.provider);
|
|
1496
|
-
this.emit("awaiting_authorization", {
|
|
1497
|
-
display_text: "Complete payment in the card authorization popup.",
|
|
1498
|
-
order_id: checkoutResult.order_id,
|
|
1499
|
-
order_number: checkoutResult.order_number
|
|
1500
|
-
});
|
|
1501
|
-
if (!this.allowPopups) {
|
|
1502
|
-
return this.fail(
|
|
1503
|
-
"PROVIDER_UNAVAILABLE",
|
|
1504
|
-
"Card payment popup is unavailable in this environment.",
|
|
1505
|
-
false
|
|
1506
|
-
);
|
|
1507
|
-
}
|
|
1508
|
-
const popupResult = await openCardPopup(
|
|
1509
|
-
provider,
|
|
1510
|
-
checkoutResult,
|
|
1511
|
-
this.checkoutData.customer.email || "customer@cimplify.io",
|
|
1512
|
-
this.getOrderCurrency(checkoutResult),
|
|
1513
|
-
this.signal
|
|
1514
|
-
);
|
|
1515
|
-
if (!popupResult.success) {
|
|
1516
|
-
const popupError = normalizeCardPopupError(popupResult.error);
|
|
1517
|
-
if (popupError === "POPUP_BLOCKED") {
|
|
1518
|
-
return this.fail(
|
|
1519
|
-
"POPUP_BLOCKED",
|
|
1520
|
-
"Unable to open card payment popup. Please allow popups and try again.",
|
|
1521
|
-
true
|
|
1522
|
-
);
|
|
1523
|
-
}
|
|
1524
|
-
if (popupError === "PROVIDER_UNAVAILABLE") {
|
|
1525
|
-
return this.fail(
|
|
1526
|
-
"PROVIDER_UNAVAILABLE",
|
|
1527
|
-
"Card payment provider is unavailable.",
|
|
1528
|
-
false
|
|
1529
|
-
);
|
|
1530
|
-
}
|
|
1531
|
-
return this.fail(
|
|
1532
|
-
"PAYMENT_CANCELLED",
|
|
1533
|
-
"Card payment was cancelled before completion.",
|
|
1534
|
-
true
|
|
1535
|
-
);
|
|
1536
|
-
}
|
|
1537
|
-
paymentReference = popupResult.reference || paymentReference;
|
|
1538
|
-
}
|
|
1539
|
-
if (checkoutResult.authorization_url) {
|
|
1540
|
-
if (typeof window !== "undefined" && this.returnUrl) {
|
|
1541
|
-
window.location.assign(checkoutResult.authorization_url);
|
|
1542
|
-
return this.fail(
|
|
1543
|
-
"REDIRECT_REQUIRED",
|
|
1544
|
-
"Redirecting to complete payment authorization.",
|
|
1545
|
-
true
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1548
|
-
if (typeof window !== "undefined") {
|
|
1549
|
-
const popup = window.open(
|
|
1550
|
-
checkoutResult.authorization_url,
|
|
1551
|
-
"cimplify-auth",
|
|
1552
|
-
"width=520,height=760,noopener,noreferrer"
|
|
1553
|
-
);
|
|
1554
|
-
if (!popup) {
|
|
1555
|
-
return this.fail(
|
|
1556
|
-
"POPUP_BLOCKED",
|
|
1557
|
-
"Authorization popup was blocked. Please allow popups and retry.",
|
|
1558
|
-
true
|
|
1559
|
-
);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
if (checkoutResult.requires_authorization || isPaymentStatusRequiresAction(checkoutResult.payment_status)) {
|
|
1564
|
-
const authorization = await this.handleAuthorization({
|
|
1565
|
-
authorizationType,
|
|
1566
|
-
paymentReference,
|
|
1567
|
-
displayText: checkoutResult.display_text,
|
|
1568
|
-
provider: checkoutResult.provider
|
|
1569
|
-
});
|
|
1570
|
-
if (!authorization.ok) {
|
|
1571
|
-
return authorization.result;
|
|
1572
|
-
}
|
|
1573
|
-
if (authorization.value) {
|
|
1574
|
-
latestCheckoutResult = authorization.value;
|
|
1575
|
-
paymentReference = authorization.value.payment_reference || paymentReference;
|
|
1576
|
-
authorizationType = normalizeAuthorizationType(authorization.value.authorization_type) || authorizationType;
|
|
1577
|
-
if (this.isSuccessfulStatus(authorization.value.payment_status)) {
|
|
1578
|
-
return this.finalizeSuccess(authorization.value);
|
|
1579
|
-
}
|
|
1580
|
-
if (this.isFailureStatus(authorization.value.payment_status)) {
|
|
1581
|
-
return this.fail(
|
|
1582
|
-
"AUTHORIZATION_FAILED",
|
|
1583
|
-
authorization.value.display_text || "Payment authorization failed.",
|
|
1584
|
-
false
|
|
1585
|
-
);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
return this.pollUntilTerminal({
|
|
1590
|
-
orderId: checkoutResult.order_id,
|
|
1591
|
-
latestCheckoutResult,
|
|
1592
|
-
paymentReference,
|
|
1593
|
-
authorizationType
|
|
1594
|
-
});
|
|
1595
|
-
} catch (error) {
|
|
1596
|
-
if (isAbortError(error)) {
|
|
1597
|
-
return this.fail("CANCELLED", "Checkout was cancelled.", true);
|
|
1598
|
-
}
|
|
1599
|
-
const message = error instanceof Error ? error.message : "Checkout failed unexpectedly.";
|
|
1600
|
-
return this.fail("CHECKOUT_FAILED", message, true);
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
async resolveNextAction(checkoutResult) {
|
|
1604
|
-
const action = checkoutResult.next_action;
|
|
1605
|
-
if (this.isSuccessfulStatus(checkoutResult.payment_status)) {
|
|
1606
|
-
return this.finalizeSuccess(checkoutResult);
|
|
1607
|
-
}
|
|
1608
|
-
let latestCheckoutResult = checkoutResult;
|
|
1609
|
-
let paymentReference = checkoutResult.payment_reference;
|
|
1610
|
-
switch (action.type) {
|
|
1611
|
-
case NEXT_ACTION_NONE:
|
|
1612
|
-
return this.finalizeSuccess(checkoutResult);
|
|
1613
|
-
case NEXT_ACTION_CARD_POPUP: {
|
|
1614
|
-
const provider = normalizeCardProvider(action.provider);
|
|
1615
|
-
this.emit("awaiting_authorization", {
|
|
1616
|
-
display_text: "Complete payment in the card authorization popup.",
|
|
1617
|
-
order_id: checkoutResult.order_id,
|
|
1618
|
-
order_number: checkoutResult.order_number
|
|
1619
|
-
});
|
|
1620
|
-
if (!this.allowPopups) {
|
|
1621
|
-
return this.fail(
|
|
1622
|
-
"PROVIDER_UNAVAILABLE",
|
|
1623
|
-
"Card payment popup is unavailable in this environment.",
|
|
1624
|
-
false
|
|
1625
|
-
);
|
|
1626
|
-
}
|
|
1627
|
-
const popupResult = await openCardPopup(
|
|
1628
|
-
provider,
|
|
1629
|
-
checkoutResult,
|
|
1630
|
-
this.checkoutData.customer.email || "customer@cimplify.io",
|
|
1631
|
-
this.getOrderCurrency(checkoutResult),
|
|
1632
|
-
this.signal
|
|
1633
|
-
);
|
|
1634
|
-
if (!popupResult.success) {
|
|
1635
|
-
const popupError = normalizeCardPopupError(popupResult.error);
|
|
1636
|
-
if (popupError === "POPUP_BLOCKED") {
|
|
1637
|
-
return this.fail(
|
|
1638
|
-
"POPUP_BLOCKED",
|
|
1639
|
-
"Unable to open card payment popup. Please allow popups and try again.",
|
|
1640
|
-
true
|
|
1641
|
-
);
|
|
1642
|
-
}
|
|
1643
|
-
if (popupError === "PROVIDER_UNAVAILABLE") {
|
|
1644
|
-
return this.fail("PROVIDER_UNAVAILABLE", "Card payment provider is unavailable.", false);
|
|
1645
|
-
}
|
|
1646
|
-
return this.fail("PAYMENT_CANCELLED", "Card payment was cancelled before completion.", true);
|
|
1647
|
-
}
|
|
1648
|
-
paymentReference = popupResult.reference || paymentReference;
|
|
1649
|
-
break;
|
|
1650
|
-
}
|
|
1651
|
-
case NEXT_ACTION_REDIRECT: {
|
|
1652
|
-
if (typeof window !== "undefined" && this.returnUrl) {
|
|
1653
|
-
window.location.assign(action.authorization_url);
|
|
1654
|
-
return this.fail("REDIRECT_REQUIRED", "Redirecting to complete payment authorization.", true);
|
|
1655
|
-
}
|
|
1656
|
-
if (typeof window !== "undefined") {
|
|
1657
|
-
const popup = window.open(
|
|
1658
|
-
action.authorization_url,
|
|
1659
|
-
"cimplify-auth",
|
|
1660
|
-
"width=520,height=760,noopener,noreferrer"
|
|
1661
|
-
);
|
|
1662
|
-
if (!popup) {
|
|
1663
|
-
return this.fail(
|
|
1664
|
-
"POPUP_BLOCKED",
|
|
1665
|
-
"Authorization popup was blocked. Please allow popups and retry.",
|
|
1666
|
-
true
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
break;
|
|
1671
|
-
}
|
|
1672
|
-
case NEXT_ACTION_AUTHORIZATION: {
|
|
1673
|
-
const authType = normalizeAuthorizationType(action.authorization_type);
|
|
1674
|
-
const authorization = await this.handleAuthorization({
|
|
1675
|
-
authorizationType: authType,
|
|
1676
|
-
paymentReference,
|
|
1677
|
-
displayText: action.display_text,
|
|
1678
|
-
provider: checkoutResult.provider
|
|
1679
|
-
});
|
|
1680
|
-
if (!authorization.ok) {
|
|
1681
|
-
return authorization.result;
|
|
1682
|
-
}
|
|
1683
|
-
if (authorization.value) {
|
|
1684
|
-
latestCheckoutResult = authorization.value;
|
|
1685
|
-
paymentReference = authorization.value.payment_reference || paymentReference;
|
|
1686
|
-
if (this.isSuccessfulStatus(authorization.value.payment_status)) {
|
|
1687
|
-
return this.finalizeSuccess(authorization.value);
|
|
1688
|
-
}
|
|
1689
|
-
if (this.isFailureStatus(authorization.value.payment_status)) {
|
|
1690
|
-
return this.fail(
|
|
1691
|
-
"AUTHORIZATION_FAILED",
|
|
1692
|
-
authorization.value.display_text || "Payment authorization failed.",
|
|
1693
|
-
false
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
break;
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
return this.pollUntilTerminal({
|
|
1701
|
-
orderId: checkoutResult.order_id,
|
|
1702
|
-
latestCheckoutResult,
|
|
1703
|
-
paymentReference,
|
|
1704
|
-
authorizationType: normalizeAuthorizationType(checkoutResult.authorization_type)
|
|
1705
|
-
});
|
|
1706
|
-
}
|
|
1707
|
-
async pollUntilTerminal(input) {
|
|
1708
|
-
let consecutiveErrors = 0;
|
|
1709
|
-
let latestCheckoutResult = input.latestCheckoutResult;
|
|
1710
|
-
let paymentReference = input.paymentReference;
|
|
1711
|
-
let authorizationType = input.authorizationType;
|
|
1712
|
-
for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) {
|
|
1713
|
-
this.ensureNotAborted();
|
|
1714
|
-
this.emit("polling", {
|
|
1715
|
-
poll_attempt: attempt,
|
|
1716
|
-
max_poll_attempts: this.maxPollAttempts,
|
|
1717
|
-
order_id: input.orderId,
|
|
1718
|
-
order_number: latestCheckoutResult.order_number
|
|
1719
|
-
});
|
|
1720
|
-
const statusResult = await this.client.checkout.pollPaymentStatus(input.orderId);
|
|
1721
|
-
if (!statusResult.ok) {
|
|
1722
|
-
consecutiveErrors += 1;
|
|
1723
|
-
if (consecutiveErrors >= MAX_CONSECUTIVE_NETWORK_ERRORS) {
|
|
1724
|
-
return this.fail(
|
|
1725
|
-
"NETWORK_ERROR",
|
|
1726
|
-
"Unable to confirm payment due to repeated network errors.",
|
|
1727
|
-
true
|
|
1728
|
-
);
|
|
1729
|
-
}
|
|
1730
|
-
await this.wait(this.pollIntervalMs);
|
|
1731
|
-
continue;
|
|
1732
|
-
}
|
|
1733
|
-
consecutiveErrors = 0;
|
|
1734
|
-
if (statusResult.value.reference) {
|
|
1735
|
-
paymentReference = statusResult.value.reference;
|
|
1736
|
-
}
|
|
1737
|
-
const normalized = normalizeStatusResponse(statusResult.value);
|
|
1738
|
-
if (normalized.paid || isPaymentStatusSuccess(normalized.status)) {
|
|
1739
|
-
return this.finalizeSuccess(latestCheckoutResult);
|
|
1740
|
-
}
|
|
1741
|
-
if (isPaymentStatusFailure(normalized.status)) {
|
|
1742
|
-
return this.fail(
|
|
1743
|
-
normalized.status ? normalized.status.toUpperCase() : "PAYMENT_FAILED",
|
|
1744
|
-
normalized.message || "Payment failed during confirmation.",
|
|
1745
|
-
false
|
|
1746
|
-
);
|
|
1747
|
-
}
|
|
1748
|
-
if (isPaymentStatusRequiresAction(normalized.status)) {
|
|
1749
|
-
const authorization = await this.handleAuthorization({
|
|
1750
|
-
authorizationType,
|
|
1751
|
-
paymentReference,
|
|
1752
|
-
displayText: normalized.message || latestCheckoutResult.display_text,
|
|
1753
|
-
provider: latestCheckoutResult.provider
|
|
1754
|
-
});
|
|
1755
|
-
if (!authorization.ok) {
|
|
1756
|
-
return authorization.result;
|
|
1757
|
-
}
|
|
1758
|
-
if (authorization.value) {
|
|
1759
|
-
latestCheckoutResult = authorization.value;
|
|
1760
|
-
paymentReference = authorization.value.payment_reference || paymentReference;
|
|
1761
|
-
authorizationType = normalizeAuthorizationType(authorization.value.authorization_type) || authorizationType;
|
|
1762
|
-
if (this.isSuccessfulStatus(authorization.value.payment_status)) {
|
|
1763
|
-
return this.finalizeSuccess(authorization.value);
|
|
1764
|
-
}
|
|
1765
|
-
if (this.isFailureStatus(authorization.value.payment_status)) {
|
|
1766
|
-
return this.fail(
|
|
1767
|
-
"AUTHORIZATION_FAILED",
|
|
1768
|
-
authorization.value.display_text || "Payment authorization failed.",
|
|
1769
|
-
false
|
|
1770
|
-
);
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
await this.wait(this.pollIntervalMs);
|
|
1775
|
-
}
|
|
1776
|
-
try {
|
|
1777
|
-
const finalResult = await this.client.checkout.pollPaymentStatus(input.orderId);
|
|
1778
|
-
if (finalResult.ok) {
|
|
1779
|
-
const normalized = normalizeStatusResponse(finalResult.value);
|
|
1780
|
-
if (normalized.paid || isPaymentStatusSuccess(normalized.status)) {
|
|
1781
|
-
return this.finalizeSuccess(latestCheckoutResult);
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
} catch {
|
|
1785
|
-
}
|
|
1786
|
-
return this.fail(
|
|
1787
|
-
"PAYMENT_TIMEOUT",
|
|
1788
|
-
"Payment confirmation timed out. Please retry checkout.",
|
|
1789
|
-
true
|
|
1790
|
-
);
|
|
1791
|
-
}
|
|
1792
|
-
async handleAuthorization(input) {
|
|
1793
|
-
const authorizationType = input.authorizationType;
|
|
1794
|
-
const paymentReference = input.paymentReference;
|
|
1795
|
-
if (!authorizationType || !paymentReference) {
|
|
1796
|
-
return { ok: true };
|
|
1797
|
-
}
|
|
1798
|
-
this.emit("awaiting_authorization", {
|
|
1799
|
-
authorization_type: authorizationType,
|
|
1800
|
-
display_text: input.displayText,
|
|
1801
|
-
provider: input.provider
|
|
1802
|
-
});
|
|
1803
|
-
let authorizationResult;
|
|
1804
|
-
const submit = async (code) => {
|
|
1805
|
-
this.ensureNotAborted();
|
|
1806
|
-
const trimmedCode = code.trim();
|
|
1807
|
-
if (!trimmedCode) {
|
|
1808
|
-
throw new Error("Authorization code is required.");
|
|
1809
|
-
}
|
|
1810
|
-
const submitResult = await this.client.checkout.submitAuthorization({
|
|
1811
|
-
reference: paymentReference,
|
|
1812
|
-
auth_type: authorizationType,
|
|
1813
|
-
value: trimmedCode
|
|
1814
|
-
});
|
|
1815
|
-
if (!submitResult.ok) {
|
|
1816
|
-
throw new Error(submitResult.error.message || "Authorization failed.");
|
|
1817
|
-
}
|
|
1818
|
-
authorizationResult = submitResult.value;
|
|
1819
|
-
};
|
|
1820
|
-
try {
|
|
1821
|
-
if (this.onAuthorizationRequired) {
|
|
1822
|
-
await this.onAuthorizationRequired(authorizationType, submit);
|
|
1823
|
-
} else {
|
|
1824
|
-
return {
|
|
1825
|
-
ok: false,
|
|
1826
|
-
result: this.fail(
|
|
1827
|
-
"AUTHORIZATION_REQUIRED",
|
|
1828
|
-
"Authorization callback is required in headless checkout mode.",
|
|
1829
|
-
false
|
|
1830
|
-
)
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
1833
|
-
} catch (error) {
|
|
1834
|
-
if (isAbortError(error)) {
|
|
1835
|
-
return {
|
|
1836
|
-
ok: false,
|
|
1837
|
-
result: this.fail("CANCELLED", "Checkout was cancelled.", true)
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1840
|
-
const message = error instanceof Error ? error.message : "Payment authorization failed.";
|
|
1841
|
-
return {
|
|
1842
|
-
ok: false,
|
|
1843
|
-
result: this.fail("AUTHORIZATION_FAILED", message, true)
|
|
1844
|
-
};
|
|
1845
|
-
}
|
|
1846
|
-
return {
|
|
1847
|
-
ok: true,
|
|
1848
|
-
value: authorizationResult
|
|
1849
|
-
};
|
|
1850
|
-
}
|
|
1851
|
-
async finalizeSuccess(checkoutResult) {
|
|
1852
|
-
this.emit("finalizing", {
|
|
1853
|
-
order_id: checkoutResult.order_id,
|
|
1854
|
-
order_number: checkoutResult.order_number
|
|
1855
|
-
});
|
|
1856
|
-
const enrolledInLink = await this.maybeEnrollInLink(checkoutResult.order_id);
|
|
1857
|
-
this.emit("success", {
|
|
1858
|
-
order_id: checkoutResult.order_id,
|
|
1859
|
-
order_number: checkoutResult.order_number
|
|
1860
|
-
});
|
|
1861
|
-
return {
|
|
1862
|
-
success: true,
|
|
1863
|
-
order: {
|
|
1864
|
-
id: checkoutResult.order_id,
|
|
1865
|
-
order_number: checkoutResult.order_number || checkoutResult.order_id,
|
|
1866
|
-
status: checkoutResult.payment_status || "paid",
|
|
1867
|
-
total: this.getOrderTotal(checkoutResult),
|
|
1868
|
-
currency: this.getOrderCurrency(checkoutResult)
|
|
1869
|
-
},
|
|
1870
|
-
enrolled_in_link: enrolledInLink
|
|
1871
|
-
};
|
|
1872
|
-
}
|
|
1873
|
-
async maybeEnrollInLink(orderId) {
|
|
1874
|
-
if (!this.enrollInLink || !orderId) {
|
|
1875
|
-
return false;
|
|
1876
|
-
}
|
|
1877
|
-
let businessId = this.businessId;
|
|
1878
|
-
if (!businessId) {
|
|
1879
|
-
const businessResult = await this.client.business.getInfo();
|
|
1880
|
-
if (!businessResult.ok || !businessResult.value?.id) {
|
|
1881
|
-
return false;
|
|
1882
|
-
}
|
|
1883
|
-
businessId = businessResult.value.id;
|
|
1884
|
-
}
|
|
1885
|
-
const address = this.getEnrollmentAddress();
|
|
1886
|
-
const mobileMoney = this.getEnrollmentMobileMoney();
|
|
1887
|
-
try {
|
|
1888
|
-
const enrollment = await this.client.link.enrollAndLinkOrder({
|
|
1889
|
-
order_id: orderId,
|
|
1890
|
-
business_id: businessId,
|
|
1891
|
-
order_type: this.orderType || this.checkoutData.order_type,
|
|
1892
|
-
address,
|
|
1893
|
-
mobile_money: mobileMoney
|
|
1894
|
-
});
|
|
1895
|
-
return enrollment.ok && enrollment.value.success;
|
|
1896
|
-
} catch {
|
|
1897
|
-
return false;
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
getEnrollmentAddress() {
|
|
1901
|
-
const source = this.addressData ?? this.checkoutData.address_info;
|
|
1902
|
-
if (!source?.street_address) {
|
|
1903
|
-
return void 0;
|
|
1904
|
-
}
|
|
1905
|
-
return {
|
|
1906
|
-
label: "Default",
|
|
1907
|
-
street_address: source.street_address,
|
|
1908
|
-
apartment: source.apartment || void 0,
|
|
1909
|
-
city: source.city || "",
|
|
1910
|
-
region: source.region || "",
|
|
1911
|
-
delivery_instructions: source.delivery_instructions || void 0,
|
|
1912
|
-
phone_for_delivery: source.phone_for_delivery || void 0
|
|
1913
|
-
};
|
|
1914
|
-
}
|
|
1915
|
-
getEnrollmentMobileMoney() {
|
|
1916
|
-
if (this.paymentData?.type === "mobile_money" && this.paymentData.phone_number && this.paymentData.provider && isValidMobileMoneyProvider(this.paymentData.provider)) {
|
|
1917
|
-
return {
|
|
1918
|
-
phone_number: this.paymentData.phone_number,
|
|
1919
|
-
provider: this.paymentData.provider,
|
|
1920
|
-
label: this.paymentData.label || "Mobile Money"
|
|
1921
|
-
};
|
|
1922
|
-
}
|
|
1923
|
-
if (this.checkoutData.mobile_money_details?.phone_number) {
|
|
1924
|
-
return {
|
|
1925
|
-
phone_number: this.checkoutData.mobile_money_details.phone_number,
|
|
1926
|
-
provider: this.checkoutData.mobile_money_details.provider,
|
|
1927
|
-
label: "Mobile Money"
|
|
1928
|
-
};
|
|
1929
|
-
}
|
|
1930
|
-
return void 0;
|
|
1931
|
-
}
|
|
1932
|
-
getOrderTotal(checkoutResult) {
|
|
1933
|
-
if (checkoutResult.fx?.pay_amount !== void 0) {
|
|
1934
|
-
return formatMoney2(checkoutResult.fx.pay_amount);
|
|
1935
|
-
}
|
|
1936
|
-
if (this.cartTotal !== void 0) {
|
|
1937
|
-
return formatMoney2(this.cartTotal);
|
|
1938
|
-
}
|
|
1939
|
-
return "0.00";
|
|
1940
|
-
}
|
|
1941
|
-
getOrderCurrency(checkoutResult) {
|
|
1942
|
-
if (checkoutResult.fx?.pay_currency) {
|
|
1943
|
-
return checkoutResult.fx.pay_currency;
|
|
1944
|
-
}
|
|
1945
|
-
if (this.cartCurrency) {
|
|
1946
|
-
return this.cartCurrency;
|
|
1947
|
-
}
|
|
1948
|
-
if (this.checkoutData.pay_currency) {
|
|
1949
|
-
return this.checkoutData.pay_currency;
|
|
1950
|
-
}
|
|
1951
|
-
return "GHS";
|
|
1952
|
-
}
|
|
1953
|
-
emit(status, context = {}) {
|
|
1954
|
-
if (!this.onStatusChange) {
|
|
1955
|
-
return;
|
|
1956
|
-
}
|
|
1957
|
-
try {
|
|
1958
|
-
this.onStatusChange(status, context);
|
|
1959
|
-
} catch {
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
fail(code, message, recoverable) {
|
|
1963
|
-
this.emit("failed", {
|
|
1964
|
-
display_text: message
|
|
1965
|
-
});
|
|
1966
|
-
return {
|
|
1967
|
-
success: false,
|
|
1968
|
-
error: {
|
|
1969
|
-
code,
|
|
1970
|
-
message,
|
|
1971
|
-
recoverable
|
|
1972
|
-
}
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
|
-
isSuccessfulStatus(status) {
|
|
1976
|
-
const normalized = normalizeStatusResponse({ status, paid: false });
|
|
1977
|
-
return normalized.paid || isPaymentStatusSuccess(normalized.status);
|
|
1978
|
-
}
|
|
1979
|
-
isFailureStatus(status) {
|
|
1980
|
-
const normalized = normalizeStatusResponse({ status, paid: false });
|
|
1981
|
-
return isPaymentStatusFailure(normalized.status);
|
|
1982
|
-
}
|
|
1983
|
-
ensureNotAborted() {
|
|
1984
|
-
if (this.signal?.aborted) {
|
|
1985
|
-
throw createAbortError();
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
wait(ms) {
|
|
1989
|
-
this.ensureNotAborted();
|
|
1990
|
-
return new Promise((resolve, reject) => {
|
|
1991
|
-
const timer = setTimeout(() => {
|
|
1992
|
-
if (this.signal) {
|
|
1993
|
-
this.signal.removeEventListener("abort", onAbort);
|
|
1994
|
-
}
|
|
1995
|
-
resolve();
|
|
1996
|
-
}, ms);
|
|
1997
|
-
const onAbort = () => {
|
|
1998
|
-
clearTimeout(timer);
|
|
1999
|
-
reject(createAbortError());
|
|
2000
|
-
};
|
|
2001
|
-
if (this.signal) {
|
|
2002
|
-
this.signal.addEventListener("abort", onAbort, { once: true });
|
|
2003
|
-
}
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
};
|
|
2007
|
-
|
|
2008
|
-
// src/checkout.ts
|
|
2009
|
-
function toTerminalFailure(code, message, recoverable) {
|
|
2010
|
-
return {
|
|
2011
|
-
success: false,
|
|
2012
|
-
error: {
|
|
2013
|
-
code,
|
|
2014
|
-
message,
|
|
2015
|
-
recoverable
|
|
2016
|
-
}
|
|
2017
|
-
};
|
|
2018
|
-
}
|
|
2019
|
-
function generateIdempotencyKey() {
|
|
2020
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
2021
|
-
return `idem_${crypto.randomUUID()}`;
|
|
2022
|
-
}
|
|
2023
|
-
const timestamp = Date.now().toString(36);
|
|
2024
|
-
const random = Math.random().toString(36).substring(2, 15);
|
|
2025
|
-
return `idem_${timestamp}_${random}`;
|
|
2026
|
-
}
|
|
2027
|
-
var CheckoutService = class {
|
|
2028
|
-
constructor(client) {
|
|
2029
|
-
this.client = client;
|
|
2030
|
-
}
|
|
2031
|
-
orderTokenParam(orderId) {
|
|
2032
|
-
const token = this.client.getOrderToken(orderId);
|
|
2033
|
-
return token ? `?token=${encodeURIComponent(token)}` : "";
|
|
2034
|
-
}
|
|
2035
|
-
async process(data) {
|
|
2036
|
-
const checkoutData = {
|
|
2037
|
-
...data,
|
|
2038
|
-
idempotency_key: data.idempotency_key || generateIdempotencyKey()
|
|
2039
|
-
};
|
|
2040
|
-
const result = await safe(
|
|
2041
|
-
this.client.post("/api/v1/checkout", {
|
|
2042
|
-
checkout_data: checkoutData
|
|
2043
|
-
})
|
|
2044
|
-
);
|
|
2045
|
-
if (result.ok && result.value.bill_token) {
|
|
2046
|
-
this.client.setOrderToken(result.value.order_id, result.value.bill_token);
|
|
2047
|
-
}
|
|
2048
|
-
return result;
|
|
2049
|
-
}
|
|
2050
|
-
async initializePayment(_orderId, _method) {
|
|
2051
|
-
return err(
|
|
2052
|
-
new CimplifyError(
|
|
2053
|
-
"NOT_IMPLEMENTED",
|
|
2054
|
-
"initializePayment is not available in REST mode.",
|
|
2055
|
-
false
|
|
2056
|
-
)
|
|
2057
|
-
);
|
|
2058
|
-
}
|
|
2059
|
-
async submitAuthorization(input) {
|
|
2060
|
-
return safe(this.client.post("/api/v1/payments/authorization", input));
|
|
2061
|
-
}
|
|
2062
|
-
async pollPaymentStatus(orderId) {
|
|
2063
|
-
const encodedId = encodeURIComponent(orderId);
|
|
2064
|
-
const tokenParam = this.orderTokenParam(orderId);
|
|
2065
|
-
return safe(this.client.get(`/api/v1/orders/${encodedId}/payment-status${tokenParam}`));
|
|
2066
|
-
}
|
|
2067
|
-
async updateOrderCustomer(orderId, customer) {
|
|
2068
|
-
const encodedId = encodeURIComponent(orderId);
|
|
2069
|
-
const tokenParam = this.orderTokenParam(orderId);
|
|
2070
|
-
return safe(this.client.post(`/api/v1/orders/${encodedId}/customer${tokenParam}`, customer));
|
|
2071
|
-
}
|
|
2072
|
-
async verifyPayment(orderId) {
|
|
2073
|
-
const encodedId = encodeURIComponent(orderId);
|
|
2074
|
-
const tokenParam = this.orderTokenParam(orderId);
|
|
2075
|
-
return safe(this.client.post(`/api/v1/orders/${encodedId}/verify-payment${tokenParam}`));
|
|
2076
|
-
}
|
|
2077
|
-
async processAndResolve(data) {
|
|
2078
|
-
data.on_status_change?.("preparing", {});
|
|
2079
|
-
if (!data.cart_id) {
|
|
2080
|
-
return ok(
|
|
2081
|
-
toTerminalFailure(
|
|
2082
|
-
"INVALID_CART",
|
|
2083
|
-
"A valid cart is required before checkout can start.",
|
|
2084
|
-
false
|
|
2085
|
-
)
|
|
2086
|
-
);
|
|
2087
|
-
}
|
|
2088
|
-
const cartResult = await this.client.cart.get();
|
|
2089
|
-
if (!cartResult.ok || !cartResult.value?.id) {
|
|
2090
|
-
return ok(
|
|
2091
|
-
toTerminalFailure(
|
|
2092
|
-
"INVALID_CART",
|
|
2093
|
-
"Unable to load cart for checkout.",
|
|
2094
|
-
false
|
|
2095
|
-
)
|
|
2096
|
-
);
|
|
2097
|
-
}
|
|
2098
|
-
const cart = cartResult.value;
|
|
2099
|
-
if (cart.id !== data.cart_id || cart.items.length === 0) {
|
|
2100
|
-
return ok(
|
|
2101
|
-
toTerminalFailure(
|
|
2102
|
-
"INVALID_CART",
|
|
2103
|
-
"Cart is empty or no longer valid. Please refresh and try again.",
|
|
2104
|
-
false
|
|
2105
|
-
)
|
|
2106
|
-
);
|
|
2107
|
-
}
|
|
2108
|
-
const checkoutData = {
|
|
2109
|
-
cart_id: data.cart_id,
|
|
2110
|
-
location_id: data.location_id,
|
|
2111
|
-
customer: data.customer,
|
|
2112
|
-
order_type: data.order_type,
|
|
2113
|
-
address_info: data.address_info,
|
|
2114
|
-
payment_method: data.payment_method,
|
|
2115
|
-
mobile_money_details: data.mobile_money_details,
|
|
2116
|
-
special_instructions: data.special_instructions,
|
|
2117
|
-
link_address_id: data.link_address_id,
|
|
2118
|
-
link_payment_method_id: data.link_payment_method_id,
|
|
2119
|
-
idempotency_key: data.idempotency_key || generateIdempotencyKey(),
|
|
2120
|
-
metadata: data.metadata,
|
|
2121
|
-
pay_currency: data.pay_currency,
|
|
2122
|
-
fx_quote_id: data.fx_quote_id
|
|
2123
|
-
};
|
|
2124
|
-
const baseCurrency = currencyCode(
|
|
2125
|
-
(cart.pricing.currency || checkoutData.pay_currency || "GHS").toUpperCase()
|
|
2126
|
-
);
|
|
2127
|
-
const payCurrency = data.pay_currency?.trim().toUpperCase();
|
|
2128
|
-
const payCurrencyCode = payCurrency ? currencyCode(payCurrency) : void 0;
|
|
2129
|
-
const cartTotalAmount = typeof cart.pricing.total_price === "number" ? cart.pricing.total_price : Number.parseFloat(cart.pricing.total_price || "0");
|
|
2130
|
-
if (payCurrencyCode && payCurrencyCode !== baseCurrency && !checkoutData.fx_quote_id && Number.isFinite(cartTotalAmount) && cartTotalAmount > 0) {
|
|
2131
|
-
const fxQuoteResult = await this.client.fx.lockQuote({
|
|
2132
|
-
from: baseCurrency,
|
|
2133
|
-
to: payCurrencyCode,
|
|
2134
|
-
amount: cart.pricing.total_price
|
|
2135
|
-
});
|
|
2136
|
-
if (!fxQuoteResult.ok) {
|
|
2137
|
-
return ok(
|
|
2138
|
-
toTerminalFailure(
|
|
2139
|
-
"FX_QUOTE_FAILED",
|
|
2140
|
-
fxQuoteResult.error.message || "Unable to lock FX quote for checkout.",
|
|
2141
|
-
true
|
|
2142
|
-
)
|
|
2143
|
-
);
|
|
2144
|
-
}
|
|
2145
|
-
checkoutData.pay_currency = payCurrencyCode;
|
|
2146
|
-
checkoutData.fx_quote_id = fxQuoteResult.value.id;
|
|
2147
|
-
}
|
|
2148
|
-
data.on_status_change?.("processing", {});
|
|
2149
|
-
const processResult = await this.process(checkoutData);
|
|
2150
|
-
if (!processResult.ok) {
|
|
2151
|
-
return ok(
|
|
2152
|
-
toTerminalFailure(
|
|
2153
|
-
processResult.error.code || "CHECKOUT_FAILED",
|
|
2154
|
-
processResult.error.message || "Unable to process checkout.",
|
|
2155
|
-
processResult.error.retryable ?? true
|
|
2156
|
-
)
|
|
2157
|
-
);
|
|
2158
|
-
}
|
|
2159
|
-
const resolver = new CheckoutResolver({
|
|
2160
|
-
client: this.client,
|
|
2161
|
-
checkoutData,
|
|
2162
|
-
pollIntervalMs: data.poll_interval_ms,
|
|
2163
|
-
maxPollAttempts: data.max_poll_attempts,
|
|
2164
|
-
onStatusChange: data.on_status_change,
|
|
2165
|
-
onAuthorizationRequired: data.on_authorization_required,
|
|
2166
|
-
signal: data.signal,
|
|
2167
|
-
returnUrl: data.return_url,
|
|
2168
|
-
enrollInLink: data.enroll_in_link,
|
|
2169
|
-
cartTotal: cart.pricing.total_price,
|
|
2170
|
-
cartCurrency: checkoutData.pay_currency || cart.pricing.currency || "GHS",
|
|
2171
|
-
orderType: checkoutData.order_type,
|
|
2172
|
-
allowPopups: data.allow_popups
|
|
2173
|
-
});
|
|
2174
|
-
const resolved = await resolver.resolve(processResult.value);
|
|
2175
|
-
return ok(resolved);
|
|
2176
|
-
}
|
|
2177
|
-
};
|
|
2178
|
-
|
|
2179
|
-
// src/orders.ts
|
|
2180
|
-
var OrderQueries = class {
|
|
2181
|
-
constructor(client) {
|
|
2182
|
-
this.client = client;
|
|
2183
|
-
}
|
|
2184
|
-
orderTokenParam(orderId) {
|
|
2185
|
-
const token = this.client.getOrderToken(orderId);
|
|
2186
|
-
return token ? `?token=${encodeURIComponent(token)}` : "";
|
|
2187
|
-
}
|
|
2188
|
-
async list(options) {
|
|
2189
|
-
const params = new URLSearchParams();
|
|
2190
|
-
if (options?.status) params.set("status", options.status);
|
|
2191
|
-
if (options?.limit) params.set("limit", String(options.limit));
|
|
2192
|
-
if (options?.offset) params.set("offset", String(options.offset));
|
|
2193
|
-
const path = params.toString() ? `/api/v1/orders?${params.toString()}` : "/api/v1/orders";
|
|
2194
|
-
return safe(this.client.get(path));
|
|
2195
|
-
}
|
|
2196
|
-
async get(orderId) {
|
|
2197
|
-
const encodedId = encodeURIComponent(orderId);
|
|
2198
|
-
const tokenParam = this.orderTokenParam(orderId);
|
|
2199
|
-
return safe(this.client.get(`/api/v1/orders/${encodedId}${tokenParam}`));
|
|
2200
|
-
}
|
|
2201
|
-
async getRecent(limit = 5) {
|
|
2202
|
-
return this.list({ limit });
|
|
2203
|
-
}
|
|
2204
|
-
async getByStatus(status) {
|
|
2205
|
-
return this.list({ status });
|
|
2206
|
-
}
|
|
2207
|
-
async cancel(orderId, reason) {
|
|
2208
|
-
const encodedId = encodeURIComponent(orderId);
|
|
2209
|
-
const tokenParam = this.orderTokenParam(orderId);
|
|
2210
|
-
return safe(
|
|
2211
|
-
this.client.post(`/api/v1/orders/${encodedId}/cancel${tokenParam}`, {
|
|
2212
|
-
reason
|
|
2213
|
-
})
|
|
2214
|
-
);
|
|
2215
|
-
}
|
|
2216
|
-
};
|
|
2217
|
-
|
|
2218
|
-
// src/link.ts
|
|
2219
|
-
function encodePathSegment(value) {
|
|
2220
|
-
return encodeURIComponent(value);
|
|
2221
|
-
}
|
|
2222
|
-
function toCreateAddressPayload(input) {
|
|
2223
|
-
return {
|
|
2224
|
-
label: input.label,
|
|
2225
|
-
street_address: input.address_line1,
|
|
2226
|
-
apartment: input.address_line2,
|
|
2227
|
-
city: input.city,
|
|
2228
|
-
region: input.state ?? "",
|
|
2229
|
-
postal_code: input.postal_code,
|
|
2230
|
-
country: input.country
|
|
2231
|
-
};
|
|
2232
|
-
}
|
|
2233
|
-
function toUpdateAddressPayload(input) {
|
|
2234
|
-
return {
|
|
2235
|
-
label: input.label,
|
|
2236
|
-
street_address: input.address_line1,
|
|
2237
|
-
apartment: input.address_line2,
|
|
2238
|
-
city: input.city,
|
|
2239
|
-
region: input.state,
|
|
2240
|
-
postal_code: input.postal_code,
|
|
2241
|
-
country: input.country
|
|
2242
|
-
};
|
|
2243
|
-
}
|
|
2244
|
-
function toCreateMobileMoneyPayload(input) {
|
|
2245
|
-
return {
|
|
2246
|
-
phone_number: input.phone_number,
|
|
2247
|
-
provider: input.provider,
|
|
2248
|
-
label: input.account_name
|
|
2249
|
-
};
|
|
2250
|
-
}
|
|
2251
|
-
var LinkService = class {
|
|
2252
|
-
constructor(client) {
|
|
2253
|
-
this.client = client;
|
|
2254
|
-
}
|
|
2255
|
-
async getProfile() {
|
|
2256
|
-
return safe(this.client.linkGet("/v1/link/profile"));
|
|
2257
|
-
}
|
|
2258
|
-
async requestOtp(input) {
|
|
2259
|
-
return safe(this.client.linkPost("/v1/link/auth/request-otp", input));
|
|
2260
|
-
}
|
|
2261
|
-
async verifyOtp(input) {
|
|
2262
|
-
const result = await safe(
|
|
2263
|
-
this.client.linkPost("/v1/link/auth/verify-otp", input)
|
|
2264
|
-
);
|
|
2265
|
-
if (result.ok && result.value.session_token) {
|
|
2266
|
-
this.client.setAccessToken(result.value.session_token);
|
|
2267
|
-
}
|
|
2268
|
-
return result;
|
|
2269
|
-
}
|
|
2270
|
-
async logout() {
|
|
2271
|
-
const result = await safe(this.client.linkPost("/v1/link/auth/logout"));
|
|
2272
|
-
if (result.ok) {
|
|
2273
|
-
this.client.clearSession();
|
|
2274
|
-
}
|
|
2275
|
-
return result;
|
|
2276
|
-
}
|
|
2277
|
-
async checkStatus(contact) {
|
|
2278
|
-
return safe(this.client.linkPost("/v1/link/check-status", { contact }));
|
|
2279
|
-
}
|
|
2280
|
-
async getLinkData() {
|
|
2281
|
-
return safe(this.client.linkGet("/v1/link/data"));
|
|
2282
|
-
}
|
|
2283
|
-
async getAddresses() {
|
|
2284
|
-
return safe(this.client.linkGet("/v1/link/addresses"));
|
|
2285
|
-
}
|
|
2286
|
-
async getMobileMoney() {
|
|
2287
|
-
return safe(this.client.linkGet("/v1/link/mobile-money"));
|
|
2288
|
-
}
|
|
2289
|
-
async getPreferences() {
|
|
2290
|
-
return safe(this.client.linkGet("/v1/link/preferences"));
|
|
2291
|
-
}
|
|
2292
|
-
async enroll(data) {
|
|
2293
|
-
return safe(this.client.linkPost("/v1/link/enroll", data));
|
|
2294
|
-
}
|
|
2295
|
-
async enrollAndLinkOrder(data) {
|
|
2296
|
-
return safe(
|
|
2297
|
-
this.client.linkPost("/v1/link/enroll-and-link-order", data)
|
|
2298
|
-
);
|
|
2299
|
-
}
|
|
2300
|
-
async updatePreferences(preferences) {
|
|
2301
|
-
return safe(this.client.linkPost("/v1/link/preferences", preferences));
|
|
2302
|
-
}
|
|
2303
|
-
async createAddress(input) {
|
|
2304
|
-
return safe(
|
|
2305
|
-
this.client.linkPost("/v1/link/addresses", toCreateAddressPayload(input))
|
|
2306
|
-
);
|
|
2307
|
-
}
|
|
2308
|
-
async updateAddress(input) {
|
|
2309
|
-
return safe(
|
|
2310
|
-
this.client.linkPost(
|
|
2311
|
-
`/v1/link/addresses/${encodePathSegment(input.address_id)}`,
|
|
2312
|
-
toUpdateAddressPayload(input)
|
|
2313
|
-
)
|
|
2314
|
-
);
|
|
2315
|
-
}
|
|
2316
|
-
async deleteAddress(addressId) {
|
|
2317
|
-
return safe(
|
|
2318
|
-
this.client.linkDelete(`/v1/link/addresses/${encodePathSegment(addressId)}`)
|
|
2319
|
-
);
|
|
2320
|
-
}
|
|
2321
|
-
async setDefaultAddress(addressId) {
|
|
2322
|
-
return safe(
|
|
2323
|
-
this.client.linkPost(
|
|
2324
|
-
`/v1/link/addresses/${encodePathSegment(addressId)}/default`
|
|
2325
|
-
)
|
|
2326
|
-
);
|
|
2327
|
-
}
|
|
2328
|
-
async trackAddressUsage(addressId) {
|
|
2329
|
-
return safe(
|
|
2330
|
-
this.client.linkPost(
|
|
2331
|
-
`/v1/link/addresses/${encodePathSegment(addressId)}/track-usage`
|
|
2332
|
-
)
|
|
2333
|
-
);
|
|
2334
|
-
}
|
|
2335
|
-
async createMobileMoney(input) {
|
|
2336
|
-
return safe(
|
|
2337
|
-
this.client.linkPost(
|
|
2338
|
-
"/v1/link/mobile-money",
|
|
2339
|
-
toCreateMobileMoneyPayload(input)
|
|
2340
|
-
)
|
|
2341
|
-
);
|
|
2342
|
-
}
|
|
2343
|
-
async deleteMobileMoney(mobileMoneyId) {
|
|
2344
|
-
return safe(
|
|
2345
|
-
this.client.linkDelete(
|
|
2346
|
-
`/v1/link/mobile-money/${encodePathSegment(mobileMoneyId)}`
|
|
2347
|
-
)
|
|
2348
|
-
);
|
|
2349
|
-
}
|
|
2350
|
-
async setDefaultMobileMoney(mobileMoneyId) {
|
|
2351
|
-
return safe(
|
|
2352
|
-
this.client.linkPost(
|
|
2353
|
-
`/v1/link/mobile-money/${encodePathSegment(mobileMoneyId)}/default`
|
|
2354
|
-
)
|
|
2355
|
-
);
|
|
2356
|
-
}
|
|
2357
|
-
async trackMobileMoneyUsage(mobileMoneyId) {
|
|
2358
|
-
return safe(
|
|
2359
|
-
this.client.linkPost(
|
|
2360
|
-
`/v1/link/mobile-money/${encodePathSegment(mobileMoneyId)}/track-usage`
|
|
2361
|
-
)
|
|
2362
|
-
);
|
|
2363
|
-
}
|
|
2364
|
-
async verifyMobileMoney(mobileMoneyId) {
|
|
2365
|
-
return safe(
|
|
2366
|
-
this.client.linkPost(
|
|
2367
|
-
`/v1/link/mobile-money/${encodePathSegment(mobileMoneyId)}/verify`
|
|
2368
|
-
)
|
|
2369
|
-
);
|
|
2370
|
-
}
|
|
2371
|
-
async getSessions() {
|
|
2372
|
-
return safe(this.client.linkGet("/v1/link/sessions"));
|
|
2373
|
-
}
|
|
2374
|
-
async revokeSession(sessionId) {
|
|
2375
|
-
return safe(this.client.linkDelete(`/v1/link/sessions/${sessionId}`));
|
|
2376
|
-
}
|
|
2377
|
-
async revokeAllSessions() {
|
|
2378
|
-
return safe(this.client.linkDelete("/v1/link/sessions"));
|
|
2379
|
-
}
|
|
2380
|
-
};
|
|
2381
|
-
|
|
2382
|
-
// src/auth.ts
|
|
2383
|
-
var AuthService = class {
|
|
2384
|
-
constructor(client) {
|
|
2385
|
-
this.client = client;
|
|
2386
|
-
}
|
|
2387
|
-
async getStatus() {
|
|
2388
|
-
return safe(this.client.get("/api/v1/auth/status"));
|
|
2389
|
-
}
|
|
2390
|
-
async getCurrentUser() {
|
|
2391
|
-
const result = await this.getStatus();
|
|
2392
|
-
if (!result.ok) return result;
|
|
2393
|
-
return ok(result.value.customer || null);
|
|
2394
|
-
}
|
|
2395
|
-
async isAuthenticated() {
|
|
2396
|
-
const result = await this.getStatus();
|
|
2397
|
-
if (!result.ok) return result;
|
|
2398
|
-
return ok(result.value.is_authenticated);
|
|
2399
|
-
}
|
|
2400
|
-
async requestOtp(contact, contactType) {
|
|
2401
|
-
return safe(
|
|
2402
|
-
this.client.post("/api/v1/auth/request-otp", {
|
|
2403
|
-
contact,
|
|
2404
|
-
contact_type: contactType
|
|
2405
|
-
})
|
|
2406
|
-
);
|
|
2407
|
-
}
|
|
2408
|
-
async verifyOtp(code, contact) {
|
|
2409
|
-
return safe(
|
|
2410
|
-
this.client.post("/api/v1/auth/verify-otp", {
|
|
2411
|
-
otp_code: code,
|
|
2412
|
-
contact
|
|
2413
|
-
})
|
|
2414
|
-
);
|
|
2415
|
-
}
|
|
2416
|
-
async logout() {
|
|
2417
|
-
return safe(this.client.post("/api/v1/auth/logout"));
|
|
2418
|
-
}
|
|
2419
|
-
async updateProfile(input) {
|
|
2420
|
-
return safe(this.client.post("/api/v1/auth/profile", input));
|
|
2421
|
-
}
|
|
2422
|
-
};
|
|
2423
|
-
|
|
2424
|
-
// src/business.ts
|
|
2425
|
-
var BusinessService = class {
|
|
2426
|
-
constructor(client) {
|
|
2427
|
-
this.client = client;
|
|
2428
|
-
}
|
|
2429
|
-
async getInfo() {
|
|
2430
|
-
return safe(this.client.get("/api/v1/business"));
|
|
2431
|
-
}
|
|
2432
|
-
async getByHandle(handle) {
|
|
2433
|
-
const encodedHandle = encodeURIComponent(handle);
|
|
2434
|
-
return safe(this.client.get(`/api/v1/business/by-handle/${encodedHandle}`));
|
|
2435
|
-
}
|
|
2436
|
-
async getByDomain(domain) {
|
|
2437
|
-
const encodedDomain = encodeURIComponent(domain);
|
|
2438
|
-
return safe(this.client.get(`/api/v1/business/by-domain?domain=${encodedDomain}`));
|
|
2439
|
-
}
|
|
2440
|
-
async getSettings() {
|
|
2441
|
-
return safe(this.client.get("/api/v1/business/settings"));
|
|
2442
|
-
}
|
|
2443
|
-
async getLocations() {
|
|
2444
|
-
return safe(this.client.get("/api/v1/business/locations"));
|
|
2445
|
-
}
|
|
2446
|
-
async getLocation(locationId) {
|
|
2447
|
-
const result = await this.getLocations();
|
|
2448
|
-
if (!result.ok) return err(result.error);
|
|
2449
|
-
const location = result.value.find((item) => item.id === locationId);
|
|
2450
|
-
if (!location) {
|
|
2451
|
-
return err(new CimplifyError(ErrorCode.NOT_FOUND, `Location not found: ${locationId}`, false));
|
|
2452
|
-
}
|
|
2453
|
-
return ok(location);
|
|
2454
|
-
}
|
|
2455
|
-
async getHours() {
|
|
2456
|
-
return safe(this.client.get("/api/v1/business/hours"));
|
|
2457
|
-
}
|
|
2458
|
-
async getLocationHours(locationId) {
|
|
2459
|
-
const result = await this.getHours();
|
|
2460
|
-
if (!result.ok) return result;
|
|
2461
|
-
return ok(result.value.filter((hour) => hour.location_id === locationId));
|
|
2462
|
-
}
|
|
2463
|
-
async getBootstrap() {
|
|
2464
|
-
return safe(this.client.get("/api/v1/bootstrap"));
|
|
2465
|
-
}
|
|
2466
|
-
};
|
|
2467
|
-
|
|
2468
|
-
// src/inventory.ts
|
|
2469
|
-
var InventoryService = class {
|
|
2470
|
-
constructor(client) {
|
|
2471
|
-
this.client = client;
|
|
2472
|
-
}
|
|
2473
|
-
withQuery(path, params) {
|
|
2474
|
-
const searchParams = new URLSearchParams();
|
|
2475
|
-
for (const [key, value] of Object.entries(params)) {
|
|
2476
|
-
if (value === void 0) continue;
|
|
2477
|
-
searchParams.set(key, String(value));
|
|
2478
|
-
}
|
|
2479
|
-
const query = searchParams.toString();
|
|
2480
|
-
return query ? `${path}?${query}` : path;
|
|
2481
|
-
}
|
|
2482
|
-
async getProductStock(productId, locationId) {
|
|
2483
|
-
const encodedId = encodeURIComponent(productId);
|
|
2484
|
-
const path = this.withQuery(`/api/v1/inventory/products/${encodedId}/stock`, {
|
|
2485
|
-
location_id: locationId
|
|
2486
|
-
});
|
|
2487
|
-
return safe(this.client.get(path));
|
|
2488
|
-
}
|
|
2489
|
-
async getVariantStock(variantId, locationId) {
|
|
2490
|
-
const encodedId = encodeURIComponent(variantId);
|
|
2491
|
-
const path = this.withQuery(`/api/v1/inventory/variants/${encodedId}/stock`, {
|
|
2492
|
-
location_id: locationId
|
|
2493
|
-
});
|
|
2494
|
-
return safe(this.client.get(path));
|
|
2495
|
-
}
|
|
2496
|
-
async checkProductAvailability(productId, quantity, locationId) {
|
|
2497
|
-
const encodedId = encodeURIComponent(productId);
|
|
2498
|
-
const path = this.withQuery(`/api/v1/inventory/products/${encodedId}/availability`, {
|
|
2499
|
-
quantity,
|
|
2500
|
-
location_id: locationId
|
|
2501
|
-
});
|
|
2502
|
-
return safe(this.client.get(path));
|
|
2503
|
-
}
|
|
2504
|
-
async checkVariantAvailability(variantId, quantity, locationId) {
|
|
2505
|
-
const encodedId = encodeURIComponent(variantId);
|
|
2506
|
-
const path = this.withQuery(`/api/v1/inventory/variants/${encodedId}/availability`, {
|
|
2507
|
-
quantity,
|
|
2508
|
-
location_id: locationId
|
|
2509
|
-
});
|
|
2510
|
-
return safe(this.client.get(path));
|
|
2511
|
-
}
|
|
2512
|
-
async checkMultipleAvailability(items, locationId) {
|
|
2513
|
-
const results = await Promise.all(
|
|
2514
|
-
items.map(
|
|
2515
|
-
(item) => item.variant_id ? this.checkVariantAvailability(item.variant_id, item.quantity, locationId) : this.checkProductAvailability(item.product_id, item.quantity, locationId)
|
|
2516
|
-
)
|
|
2517
|
-
);
|
|
2518
|
-
for (const result of results) {
|
|
2519
|
-
if (!result.ok) return result;
|
|
2520
|
-
}
|
|
2521
|
-
return ok(results.map((r) => r.value));
|
|
2522
|
-
}
|
|
2523
|
-
async isInStock(productId, locationId) {
|
|
2524
|
-
const result = await this.checkProductAvailability(productId, 1, locationId);
|
|
2525
|
-
if (!result.ok) return result;
|
|
2526
|
-
return ok(result.value.is_available);
|
|
2527
|
-
}
|
|
2528
|
-
async getAvailableQuantity(productId, locationId) {
|
|
2529
|
-
const result = await this.getProductStock(productId, locationId);
|
|
2530
|
-
if (!result.ok) return result;
|
|
2531
|
-
return ok(result.value.available_quantity);
|
|
2532
|
-
}
|
|
2533
|
-
};
|
|
2534
|
-
|
|
2535
|
-
// src/scheduling.ts
|
|
2536
|
-
function withQuery2(path, params) {
|
|
2537
|
-
const searchParams = new URLSearchParams();
|
|
2538
|
-
for (const [key, value] of Object.entries(params)) {
|
|
2539
|
-
if (value === void 0) continue;
|
|
2540
|
-
searchParams.set(key, String(value));
|
|
2541
|
-
}
|
|
2542
|
-
const query = searchParams.toString();
|
|
2543
|
-
return query ? `${path}?${query}` : path;
|
|
2544
|
-
}
|
|
2545
|
-
function firstScheduledTime(booking) {
|
|
2546
|
-
return booking.service_items.find((item) => typeof item.scheduled_start === "string")?.scheduled_start || booking.created_at;
|
|
2547
|
-
}
|
|
2548
|
-
var SchedulingService = class {
|
|
2549
|
-
constructor(client) {
|
|
2550
|
-
this.client = client;
|
|
2551
|
-
}
|
|
2552
|
-
async getServices() {
|
|
2553
|
-
return safe(this.client.get("/api/v1/scheduling/services"));
|
|
2554
|
-
}
|
|
2555
|
-
async getService(serviceId) {
|
|
2556
|
-
const encodedId = encodeURIComponent(serviceId);
|
|
2557
|
-
return safe(this.client.get(`/api/v1/scheduling/services/${encodedId}`));
|
|
2558
|
-
}
|
|
2559
|
-
async getAvailableSlots(input) {
|
|
2560
|
-
const path = withQuery2("/api/v1/scheduling/slots", {
|
|
2561
|
-
service_id: input.service_id,
|
|
2562
|
-
date: input.date,
|
|
2563
|
-
participant_count: input.participant_count,
|
|
2564
|
-
duration_minutes: input.duration_minutes
|
|
2565
|
-
});
|
|
2566
|
-
return safe(this.client.get(path));
|
|
2567
|
-
}
|
|
2568
|
-
async checkSlotAvailability(input) {
|
|
2569
|
-
const path = withQuery2("/api/v1/scheduling/slots/check", {
|
|
2570
|
-
service_id: input.service_id,
|
|
2571
|
-
slot_time: input.slot_time,
|
|
2572
|
-
duration_minutes: input.duration_minutes,
|
|
2573
|
-
participant_count: input.participant_count
|
|
2574
|
-
});
|
|
2575
|
-
return safe(this.client.get(path));
|
|
2576
|
-
}
|
|
2577
|
-
async getServiceAvailability(params) {
|
|
2578
|
-
const path = withQuery2("/api/v1/scheduling/availability", {
|
|
2579
|
-
service_id: params.service_id,
|
|
2580
|
-
start_date: params.start_date,
|
|
2581
|
-
end_date: params.end_date,
|
|
2582
|
-
location_id: params.location_id,
|
|
2583
|
-
participant_count: params.participant_count
|
|
2584
|
-
});
|
|
2585
|
-
return safe(this.client.get(path));
|
|
2586
|
-
}
|
|
2587
|
-
async getBooking(bookingId) {
|
|
2588
|
-
const encodedId = encodeURIComponent(bookingId);
|
|
2589
|
-
return safe(this.client.get(`/api/v1/scheduling/bookings/${encodedId}`));
|
|
2590
|
-
}
|
|
2591
|
-
async getCustomerBookings(customerId) {
|
|
2592
|
-
const path = withQuery2("/api/v1/scheduling/bookings", {
|
|
2593
|
-
customer_id: customerId
|
|
2594
|
-
});
|
|
2595
|
-
return safe(this.client.get(path));
|
|
2596
|
-
}
|
|
2597
|
-
async getUpcomingBookings() {
|
|
2598
|
-
const result = await this.getCustomerBookings();
|
|
2599
|
-
if (!result.ok) return result;
|
|
2600
|
-
const upcoming = result.value.filter((booking) => {
|
|
2601
|
-
const status = String(booking.status).toLowerCase();
|
|
2602
|
-
return status !== "completed" && status !== "cancelled";
|
|
2603
|
-
}).sort((a, b) => firstScheduledTime(a).localeCompare(firstScheduledTime(b)));
|
|
2604
|
-
return ok(upcoming);
|
|
2605
|
-
}
|
|
2606
|
-
async getPastBookings(limit = 10) {
|
|
2607
|
-
const result = await this.getCustomerBookings();
|
|
2608
|
-
if (!result.ok) return result;
|
|
2609
|
-
const past = result.value.filter((booking) => String(booking.status).toLowerCase() === "completed").sort((a, b) => firstScheduledTime(b).localeCompare(firstScheduledTime(a))).slice(0, Math.max(0, limit));
|
|
2610
|
-
return ok(past);
|
|
2611
|
-
}
|
|
2612
|
-
async cancelBooking(input) {
|
|
2613
|
-
const encodedId = encodeURIComponent(input.booking_id);
|
|
2614
|
-
return safe(
|
|
2615
|
-
this.client.post(`/api/v1/scheduling/bookings/${encodedId}/cancel`, {
|
|
2616
|
-
reason: input.reason
|
|
2617
|
-
})
|
|
2618
|
-
);
|
|
2619
|
-
}
|
|
2620
|
-
async rescheduleBooking(input) {
|
|
2621
|
-
return safe(
|
|
2622
|
-
this.client.post("/api/v1/scheduling/bookings/reschedule", input)
|
|
2623
|
-
);
|
|
2624
|
-
}
|
|
2625
|
-
async getNextAvailableSlot(serviceId, fromDate) {
|
|
2626
|
-
const date = fromDate || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2627
|
-
const result = await this.getAvailableSlots({
|
|
2628
|
-
service_id: serviceId,
|
|
2629
|
-
date
|
|
2630
|
-
});
|
|
2631
|
-
if (!result.ok) return result;
|
|
2632
|
-
return ok(result.value.find((slot) => slot.is_available) || null);
|
|
2633
|
-
}
|
|
2634
|
-
async hasAvailabilityOn(serviceId, date) {
|
|
2635
|
-
const result = await this.getAvailableSlots({
|
|
2636
|
-
service_id: serviceId,
|
|
2637
|
-
date
|
|
2638
|
-
});
|
|
2639
|
-
if (!result.ok) return result;
|
|
2640
|
-
return ok(result.value.some((slot) => slot.is_available));
|
|
2641
|
-
}
|
|
2642
|
-
};
|
|
2643
|
-
|
|
2644
|
-
// src/lite.ts
|
|
2645
|
-
var LiteService = class {
|
|
2646
|
-
constructor(client) {
|
|
2647
|
-
this.client = client;
|
|
2648
|
-
}
|
|
2649
|
-
async getBootstrap() {
|
|
2650
|
-
return safe(this.client.get("/api/v1/lite/bootstrap"));
|
|
2651
|
-
}
|
|
2652
|
-
async getTable(tableId) {
|
|
2653
|
-
const encodedId = encodeURIComponent(tableId);
|
|
2654
|
-
return safe(this.client.get(`/api/v1/lite/tables/${encodedId}`));
|
|
2655
|
-
}
|
|
2656
|
-
async getMenu() {
|
|
2657
|
-
return safe(this.client.get("/api/v1/lite/menu"));
|
|
2658
|
-
}
|
|
2659
|
-
async getMenuByCategory(categoryId) {
|
|
2660
|
-
const encodedId = encodeURIComponent(categoryId);
|
|
2661
|
-
return safe(this.client.get(`/api/v1/lite/menu/categories/${encodedId}`));
|
|
2662
|
-
}
|
|
2663
|
-
};
|
|
2664
|
-
|
|
2665
|
-
// src/fx.ts
|
|
2666
|
-
var FxService = class {
|
|
2667
|
-
constructor(client) {
|
|
2668
|
-
this.client = client;
|
|
2669
|
-
}
|
|
2670
|
-
async getRate(from, to) {
|
|
2671
|
-
const path = `/api/v1/fx/rate?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`;
|
|
2672
|
-
return safe(this.client.get(path));
|
|
2673
|
-
}
|
|
2674
|
-
async lockQuote(request) {
|
|
2675
|
-
return safe(this.client.post("/api/v1/fx/quotes", request));
|
|
2676
|
-
}
|
|
2677
|
-
};
|
|
2678
|
-
|
|
2679
|
-
// src/activity.ts
|
|
2680
|
-
var ActivityService = class {
|
|
2681
|
-
constructor(client) {
|
|
2682
|
-
this.client = client;
|
|
2683
|
-
}
|
|
2684
|
-
trackProductView(productId, options = {}) {
|
|
2685
|
-
const event = {
|
|
2686
|
-
type: "product_view",
|
|
2687
|
-
product_id: productId,
|
|
2688
|
-
product_name: options.productName,
|
|
2689
|
-
category_id: options.categoryId,
|
|
2690
|
-
price: options.price,
|
|
2691
|
-
page_path: options.pagePath ?? currentPath()
|
|
2692
|
-
};
|
|
2693
|
-
this.sendEvents([event]);
|
|
2694
|
-
}
|
|
2695
|
-
trackCategoryView(categoryId, options = {}) {
|
|
2696
|
-
const event = {
|
|
2697
|
-
type: "category_view",
|
|
2698
|
-
category_id: categoryId,
|
|
2699
|
-
category_name: options.categoryName,
|
|
2700
|
-
page_path: options.pagePath ?? currentPath()
|
|
2701
|
-
};
|
|
2702
|
-
this.sendEvents([event]);
|
|
2703
|
-
}
|
|
2704
|
-
sendEvents(events) {
|
|
2705
|
-
try {
|
|
2706
|
-
this.client.post("/api/v1/activity/events", { events }).catch(() => {
|
|
2707
|
-
});
|
|
2708
|
-
} catch {
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
};
|
|
2712
|
-
function currentPath() {
|
|
2713
|
-
if (typeof window !== "undefined") {
|
|
2714
|
-
return window.location.pathname;
|
|
2715
|
-
}
|
|
2716
|
-
return void 0;
|
|
2717
|
-
}
|
|
2718
|
-
|
|
2719
|
-
// src/types/elements.ts
|
|
2720
|
-
var ELEMENT_TYPES = {
|
|
2721
|
-
AUTH: "auth",
|
|
2722
|
-
ADDRESS: "address",
|
|
2723
|
-
PAYMENT: "payment",
|
|
2724
|
-
CHECKOUT: "checkout"
|
|
2725
|
-
};
|
|
2726
|
-
var MESSAGE_TYPES = {
|
|
2727
|
-
INIT: "init",
|
|
2728
|
-
SET_TOKEN: "set_token",
|
|
2729
|
-
SET_CART: "set_cart",
|
|
2730
|
-
GET_DATA: "get_data",
|
|
2731
|
-
PROCESS_CHECKOUT: "process_checkout",
|
|
2732
|
-
ABORT_CHECKOUT: "abort_checkout",
|
|
2733
|
-
READY: "ready",
|
|
2734
|
-
HEIGHT_CHANGE: "height_change",
|
|
2735
|
-
AUTHENTICATED: "authenticated",
|
|
2736
|
-
REQUIRES_OTP: "requires_otp",
|
|
2737
|
-
ERROR: "error",
|
|
2738
|
-
ADDRESS_CHANGED: "address_changed",
|
|
2739
|
-
ADDRESS_SELECTED: "address_selected",
|
|
2740
|
-
PAYMENT_METHOD_SELECTED: "payment_method_selected",
|
|
2741
|
-
TOKEN_REFRESHED: "token_refreshed",
|
|
2742
|
-
LOGOUT_COMPLETE: "logout_complete",
|
|
2743
|
-
CONTACT_PROVIDED: "contact_provided",
|
|
2744
|
-
CHECKOUT_STATUS: "checkout_status",
|
|
2745
|
-
CHECKOUT_COMPLETE: "checkout_complete",
|
|
2746
|
-
ORDER_TYPE_CHANGED: "order_type_changed",
|
|
2747
|
-
REQUEST_SUBMIT: "request_submit"
|
|
2748
|
-
};
|
|
2749
|
-
var EVENT_TYPES = {
|
|
2750
|
-
READY: "ready",
|
|
2751
|
-
AUTHENTICATED: "authenticated",
|
|
2752
|
-
REQUIRES_OTP: "requires_otp",
|
|
2753
|
-
ERROR: "error",
|
|
2754
|
-
CHANGE: "change",
|
|
2755
|
-
ORDER_TYPE_CHANGED: "order_type_changed",
|
|
2756
|
-
REQUEST_SUBMIT: "request_submit"
|
|
2757
|
-
};
|
|
2758
|
-
|
|
2759
|
-
// src/elements.ts
|
|
2760
|
-
function toCheckoutError(code, message, recoverable) {
|
|
2761
|
-
const hint = getErrorHint(code);
|
|
2762
|
-
return {
|
|
2763
|
-
success: false,
|
|
2764
|
-
error: {
|
|
2765
|
-
code,
|
|
2766
|
-
message,
|
|
2767
|
-
recoverable,
|
|
2768
|
-
docs_url: hint?.docs_url,
|
|
2769
|
-
suggestion: hint?.suggestion
|
|
2770
|
-
}
|
|
2771
|
-
};
|
|
2772
|
-
}
|
|
2773
|
-
var DEFAULT_LINK_URL = "https://link.cimplify.io";
|
|
2774
|
-
function generateNonce() {
|
|
2775
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
2776
|
-
return crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
2777
|
-
}
|
|
2778
|
-
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
2779
|
-
}
|
|
2780
|
-
function isAllowedOrigin(origin) {
|
|
2781
|
-
try {
|
|
2782
|
-
const url = new URL(origin);
|
|
2783
|
-
const hostname = url.hostname;
|
|
2784
|
-
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
2785
|
-
return true;
|
|
2786
|
-
}
|
|
2787
|
-
if (url.protocol !== "https:") {
|
|
2788
|
-
return false;
|
|
2789
|
-
}
|
|
2790
|
-
return hostname === "cimplify.io" || hostname.endsWith(".cimplify.io");
|
|
2791
|
-
} catch {
|
|
2792
|
-
return false;
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
function parseIframeMessage(data) {
|
|
2796
|
-
if (!data || typeof data !== "object") {
|
|
2797
|
-
return null;
|
|
2798
|
-
}
|
|
2799
|
-
const maybeType = data.type;
|
|
2800
|
-
if (typeof maybeType !== "string") {
|
|
2801
|
-
return null;
|
|
2802
|
-
}
|
|
2803
|
-
return data;
|
|
2804
|
-
}
|
|
2805
|
-
var CimplifyElements = class {
|
|
2806
|
-
constructor(client, businessId, options = {}) {
|
|
2807
|
-
this.elements = /* @__PURE__ */ new Map();
|
|
2808
|
-
this.accessToken = null;
|
|
2809
|
-
this.accountId = null;
|
|
2810
|
-
this.customerId = null;
|
|
2811
|
-
this.customerData = null;
|
|
2812
|
-
this.addressData = null;
|
|
2813
|
-
this.paymentData = null;
|
|
2814
|
-
this.checkoutInProgress = false;
|
|
2815
|
-
this.activeCheckoutAbort = null;
|
|
2816
|
-
this.businessIdResolvePromise = null;
|
|
2817
|
-
this.client = client;
|
|
2818
|
-
this.businessId = businessId ?? null;
|
|
2819
|
-
this.linkUrl = options.linkUrl || DEFAULT_LINK_URL;
|
|
2820
|
-
this.options = options;
|
|
2821
|
-
this.boundHandleMessage = this.handleMessage.bind(this);
|
|
2822
|
-
if (typeof window !== "undefined") {
|
|
2823
|
-
window.addEventListener("message", this.boundHandleMessage);
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
2826
|
-
debug(...args) {
|
|
2827
|
-
if (this.client.isTestMode()) {
|
|
2828
|
-
console.debug(...args);
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
create(type, options = {}) {
|
|
2832
|
-
const existing = this.elements.get(type);
|
|
2833
|
-
if (existing) return existing;
|
|
2834
|
-
const element = new CimplifyElement(type, this.businessId, this.linkUrl, options, this);
|
|
2835
|
-
this.elements.set(type, element);
|
|
2836
|
-
return element;
|
|
2837
|
-
}
|
|
2838
|
-
getElement(type) {
|
|
2839
|
-
return this.elements.get(type);
|
|
2840
|
-
}
|
|
2841
|
-
_removeElement(type, instance) {
|
|
2842
|
-
const current = this.elements.get(type);
|
|
2843
|
-
if (current === instance) {
|
|
2844
|
-
this.elements.delete(type);
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
destroy() {
|
|
2848
|
-
this.elements.forEach((element) => element.destroy());
|
|
2849
|
-
this.elements.clear();
|
|
2850
|
-
if (typeof window !== "undefined") {
|
|
2851
|
-
window.removeEventListener("message", this.boundHandleMessage);
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
async submitCheckout(data) {
|
|
2855
|
-
const result = await this.processCheckout({
|
|
2856
|
-
cart_id: data.cart_id,
|
|
2857
|
-
order_type: data.order_type ?? "delivery",
|
|
2858
|
-
location_id: data.location_id,
|
|
2859
|
-
notes: data.notes,
|
|
2860
|
-
scheduled_time: data.scheduled_time,
|
|
2861
|
-
tip_amount: data.tip_amount,
|
|
2862
|
-
enroll_in_link: true
|
|
2863
|
-
});
|
|
2864
|
-
if (result.success) {
|
|
2865
|
-
return {
|
|
2866
|
-
success: true,
|
|
2867
|
-
order: {
|
|
2868
|
-
id: result.order?.id || "",
|
|
2869
|
-
status: result.order?.status || "unknown",
|
|
2870
|
-
total: result.order?.total || ""
|
|
2871
|
-
}
|
|
2872
|
-
};
|
|
2873
|
-
}
|
|
2874
|
-
return {
|
|
2875
|
-
success: false,
|
|
2876
|
-
error: {
|
|
2877
|
-
code: result.error?.code || "CHECKOUT_FAILED",
|
|
2878
|
-
message: result.error?.message || "Checkout failed"
|
|
2879
|
-
}
|
|
2880
|
-
};
|
|
2881
|
-
}
|
|
2882
|
-
processCheckout(options) {
|
|
2883
|
-
let abortFn = null;
|
|
2884
|
-
const task = (async () => {
|
|
2885
|
-
this.debug("[cimplify:checkout] processCheckout called", { cart_id: options.cart_id, order_type: options.order_type, location_id: options.location_id });
|
|
2886
|
-
if (this.checkoutInProgress) {
|
|
2887
|
-
this.debug("[cimplify:checkout] BLOCKED: already processing");
|
|
2888
|
-
return toCheckoutError(
|
|
2889
|
-
"ALREADY_PROCESSING",
|
|
2890
|
-
"Checkout is already in progress.",
|
|
2891
|
-
false
|
|
2892
|
-
);
|
|
2893
|
-
}
|
|
2894
|
-
this.checkoutInProgress = true;
|
|
2895
|
-
if (!options.cart_id) {
|
|
2896
|
-
this.debug("[cimplify:checkout] BLOCKED: no cart_id");
|
|
2897
|
-
this.checkoutInProgress = false;
|
|
2898
|
-
return toCheckoutError(
|
|
2899
|
-
"INVALID_CART",
|
|
2900
|
-
"A valid cart is required before checkout can start.",
|
|
2901
|
-
false
|
|
2902
|
-
);
|
|
2903
|
-
}
|
|
2904
|
-
if (!options.order_type) {
|
|
2905
|
-
this.debug("[cimplify:checkout] BLOCKED: no order_type");
|
|
2906
|
-
this.checkoutInProgress = false;
|
|
2907
|
-
return toCheckoutError(
|
|
2908
|
-
"ORDER_TYPE_REQUIRED",
|
|
2909
|
-
"Order type is required before checkout can start.",
|
|
2910
|
-
false
|
|
2911
|
-
);
|
|
2912
|
-
}
|
|
2913
|
-
const checkoutElement = this.elements.get(ELEMENT_TYPES.CHECKOUT) || this.elements.get(ELEMENT_TYPES.PAYMENT);
|
|
2914
|
-
if (!checkoutElement) {
|
|
2915
|
-
this.debug("[cimplify:checkout] BLOCKED: no checkout element");
|
|
2916
|
-
this.checkoutInProgress = false;
|
|
2917
|
-
return toCheckoutError(
|
|
2918
|
-
"NO_PAYMENT_ELEMENT",
|
|
2919
|
-
"Checkout element must be mounted before checkout.",
|
|
2920
|
-
false
|
|
2921
|
-
);
|
|
2922
|
-
}
|
|
2923
|
-
if (!checkoutElement.isMounted()) {
|
|
2924
|
-
this.debug("[cimplify:checkout] BLOCKED: checkout element not mounted");
|
|
2925
|
-
this.checkoutInProgress = false;
|
|
2926
|
-
return toCheckoutError(
|
|
2927
|
-
"PAYMENT_NOT_MOUNTED",
|
|
2928
|
-
"Checkout element must be mounted before checkout.",
|
|
2929
|
-
false
|
|
2930
|
-
);
|
|
2931
|
-
}
|
|
2932
|
-
const isUnifiedCheckout = this.elements.has(ELEMENT_TYPES.CHECKOUT);
|
|
2933
|
-
if (!isUnifiedCheckout) {
|
|
2934
|
-
const authElement = this.elements.get(ELEMENT_TYPES.AUTH);
|
|
2935
|
-
if (authElement && !this.accessToken) {
|
|
2936
|
-
this.debug("[cimplify:checkout] BLOCKED: auth incomplete");
|
|
2937
|
-
this.checkoutInProgress = false;
|
|
2938
|
-
return toCheckoutError(
|
|
2939
|
-
"AUTH_INCOMPLETE",
|
|
2940
|
-
"Authentication must complete before checkout can start.",
|
|
2941
|
-
true
|
|
2942
|
-
);
|
|
2943
|
-
}
|
|
2944
|
-
const addressElement = this.elements.get(ELEMENT_TYPES.ADDRESS);
|
|
2945
|
-
if (addressElement) {
|
|
2946
|
-
this.debug("[cimplify:checkout] getting address data...");
|
|
2947
|
-
await addressElement.getData();
|
|
2948
|
-
this.debug("[cimplify:checkout] address data resolved", this.addressData);
|
|
2949
|
-
}
|
|
2950
|
-
await this.hydrateCustomerData();
|
|
2951
|
-
}
|
|
2952
|
-
this.debug("[cimplify:checkout] customer data hydrated", { customerData: this.customerData, accountId: this.accountId, isUnifiedCheckout });
|
|
2953
|
-
const processMessage = {
|
|
2954
|
-
type: MESSAGE_TYPES.PROCESS_CHECKOUT,
|
|
2955
|
-
cart_id: options.cart_id,
|
|
2956
|
-
order_type: options.order_type,
|
|
2957
|
-
location_id: options.location_id,
|
|
2958
|
-
notes: options.notes,
|
|
2959
|
-
scheduled_time: options.scheduled_time,
|
|
2960
|
-
tip_amount: options.tip_amount,
|
|
2961
|
-
pay_currency: options.pay_currency,
|
|
2962
|
-
enroll_in_link: options.enroll_in_link ?? true,
|
|
2963
|
-
address: isUnifiedCheckout ? void 0 : this.addressData ?? void 0,
|
|
2964
|
-
customer: isUnifiedCheckout ? void 0 : this.customerData ? {
|
|
2965
|
-
name: this.customerData.name,
|
|
2966
|
-
email: this.customerData.email,
|
|
2967
|
-
phone: this.customerData.phone
|
|
2968
|
-
} : null,
|
|
2969
|
-
account_id: this.accountId ?? void 0,
|
|
2970
|
-
customer_id: this.customerId ?? void 0
|
|
2971
|
-
};
|
|
2972
|
-
const timeoutMs = options.timeout_ms ?? 18e4;
|
|
2973
|
-
const paymentWindow = checkoutElement.getContentWindow();
|
|
2974
|
-
return new Promise((resolve) => {
|
|
2975
|
-
let settled = false;
|
|
2976
|
-
const cleanup = () => {
|
|
2977
|
-
if (typeof window !== "undefined") {
|
|
2978
|
-
window.removeEventListener("message", handleCheckoutMessage);
|
|
2979
|
-
}
|
|
2980
|
-
clearTimeout(timeout);
|
|
2981
|
-
this.checkoutInProgress = false;
|
|
2982
|
-
this.activeCheckoutAbort = null;
|
|
2983
|
-
};
|
|
2984
|
-
const settle = (result) => {
|
|
2985
|
-
if (settled) {
|
|
2986
|
-
return;
|
|
2987
|
-
}
|
|
2988
|
-
settled = true;
|
|
2989
|
-
cleanup();
|
|
2990
|
-
resolve(result);
|
|
2991
|
-
};
|
|
2992
|
-
const timeout = setTimeout(() => {
|
|
2993
|
-
settle(
|
|
2994
|
-
toCheckoutError(
|
|
2995
|
-
"TIMEOUT",
|
|
2996
|
-
"Checkout timed out before receiving a terminal response.",
|
|
2997
|
-
true
|
|
2998
|
-
)
|
|
2999
|
-
);
|
|
3000
|
-
}, timeoutMs);
|
|
3001
|
-
const handleCheckoutMessage = (event) => {
|
|
3002
|
-
if (!isAllowedOrigin(event.origin)) {
|
|
3003
|
-
return;
|
|
3004
|
-
}
|
|
3005
|
-
const message = parseIframeMessage(event.data);
|
|
3006
|
-
if (!message) {
|
|
3007
|
-
return;
|
|
3008
|
-
}
|
|
3009
|
-
if (message.type === MESSAGE_TYPES.LOGOUT_COMPLETE) {
|
|
3010
|
-
checkoutElement.sendMessage({ type: MESSAGE_TYPES.ABORT_CHECKOUT });
|
|
3011
|
-
settle(
|
|
3012
|
-
toCheckoutError(
|
|
3013
|
-
"AUTH_LOST",
|
|
3014
|
-
"Authentication was cleared during checkout.",
|
|
3015
|
-
true
|
|
3016
|
-
)
|
|
3017
|
-
);
|
|
3018
|
-
return;
|
|
3019
|
-
}
|
|
3020
|
-
if (paymentWindow && event.source && event.source !== paymentWindow) {
|
|
3021
|
-
return;
|
|
3022
|
-
}
|
|
3023
|
-
if (message.type === MESSAGE_TYPES.CHECKOUT_STATUS) {
|
|
3024
|
-
options.on_status_change?.(message.status, message.context);
|
|
3025
|
-
return;
|
|
3026
|
-
}
|
|
3027
|
-
if (message.type === MESSAGE_TYPES.CHECKOUT_COMPLETE) {
|
|
3028
|
-
this.debug("[cimplify:checkout] received checkout_complete", { success: message.success, error: message.error });
|
|
3029
|
-
settle({
|
|
3030
|
-
success: message.success,
|
|
3031
|
-
order: message.order,
|
|
3032
|
-
error: message.error,
|
|
3033
|
-
enrolled_in_link: message.enrolled_in_link
|
|
3034
|
-
});
|
|
3035
|
-
}
|
|
3036
|
-
};
|
|
3037
|
-
if (typeof window !== "undefined") {
|
|
3038
|
-
window.addEventListener("message", handleCheckoutMessage);
|
|
3039
|
-
}
|
|
3040
|
-
abortFn = () => {
|
|
3041
|
-
checkoutElement.sendMessage({ type: MESSAGE_TYPES.ABORT_CHECKOUT });
|
|
3042
|
-
settle(
|
|
3043
|
-
toCheckoutError(
|
|
3044
|
-
"CANCELLED",
|
|
3045
|
-
"Checkout was cancelled.",
|
|
3046
|
-
true
|
|
3047
|
-
)
|
|
3048
|
-
);
|
|
3049
|
-
};
|
|
3050
|
-
this.activeCheckoutAbort = abortFn;
|
|
3051
|
-
this.debug("[cimplify:checkout] sending process_checkout to checkout iframe", { cart_id: processMessage.cart_id, order_type: processMessage.order_type, hasCustomer: !!processMessage.customer, hasAddress: !!processMessage.address, isUnifiedCheckout });
|
|
3052
|
-
checkoutElement.sendMessage(processMessage);
|
|
3053
|
-
this.debug("[cimplify:checkout] postMessage sent, waiting for checkout_complete...");
|
|
3054
|
-
});
|
|
3055
|
-
})();
|
|
3056
|
-
const abortable = task;
|
|
3057
|
-
abortable.abort = () => {
|
|
3058
|
-
if (abortFn) {
|
|
3059
|
-
abortFn();
|
|
3060
|
-
}
|
|
3061
|
-
};
|
|
3062
|
-
return abortable;
|
|
3063
|
-
}
|
|
3064
|
-
isAuthenticated() {
|
|
3065
|
-
return this.accessToken !== null;
|
|
3066
|
-
}
|
|
3067
|
-
getAccessToken() {
|
|
3068
|
-
return this.accessToken;
|
|
3069
|
-
}
|
|
3070
|
-
getPublicKey() {
|
|
3071
|
-
return this.client.getPublicKey();
|
|
3072
|
-
}
|
|
3073
|
-
getBusinessId() {
|
|
3074
|
-
return this.businessId;
|
|
3075
|
-
}
|
|
3076
|
-
async resolveBusinessId() {
|
|
3077
|
-
if (this.businessId) {
|
|
3078
|
-
return this.businessId;
|
|
3079
|
-
}
|
|
3080
|
-
if (this.businessIdResolvePromise) {
|
|
3081
|
-
return this.businessIdResolvePromise;
|
|
3082
|
-
}
|
|
3083
|
-
this.businessIdResolvePromise = this.client.resolveBusinessId().then((resolvedBusinessId) => {
|
|
3084
|
-
this.businessId = resolvedBusinessId;
|
|
3085
|
-
return resolvedBusinessId;
|
|
3086
|
-
}).catch(() => null).finally(() => {
|
|
3087
|
-
this.businessIdResolvePromise = null;
|
|
3088
|
-
});
|
|
3089
|
-
return this.businessIdResolvePromise;
|
|
3090
|
-
}
|
|
3091
|
-
getAppearance() {
|
|
3092
|
-
return this.options.appearance;
|
|
3093
|
-
}
|
|
3094
|
-
async hydrateCustomerData() {
|
|
3095
|
-
if (!this.accessToken || this.customerData) {
|
|
3096
|
-
return;
|
|
3097
|
-
}
|
|
3098
|
-
if (!this.client.getAccessToken()) {
|
|
3099
|
-
this.client.setAccessToken(this.accessToken);
|
|
3100
|
-
}
|
|
3101
|
-
const linkDataResult = await this.client.link.getLinkData();
|
|
3102
|
-
if (!linkDataResult.ok || !linkDataResult.value?.customer) {
|
|
3103
|
-
return;
|
|
3104
|
-
}
|
|
3105
|
-
const customer = linkDataResult.value.customer;
|
|
3106
|
-
this.customerData = {
|
|
3107
|
-
name: customer.name || "",
|
|
3108
|
-
email: customer.email || null,
|
|
3109
|
-
phone: customer.phone || null
|
|
3110
|
-
};
|
|
3111
|
-
}
|
|
3112
|
-
handleMessage(event) {
|
|
3113
|
-
if (!isAllowedOrigin(event.origin)) {
|
|
3114
|
-
return;
|
|
3115
|
-
}
|
|
3116
|
-
const message = parseIframeMessage(event.data);
|
|
3117
|
-
if (!message) return;
|
|
3118
|
-
switch (message.type) {
|
|
3119
|
-
case MESSAGE_TYPES.AUTHENTICATED:
|
|
3120
|
-
const customer = message.customer ?? {
|
|
3121
|
-
name: "",
|
|
3122
|
-
email: null,
|
|
3123
|
-
phone: null
|
|
3124
|
-
};
|
|
3125
|
-
this.accessToken = message.token;
|
|
3126
|
-
this.accountId = message.accountId;
|
|
3127
|
-
this.customerId = message.customerId;
|
|
3128
|
-
this.customerData = customer;
|
|
3129
|
-
this.client.setAccessToken(message.token);
|
|
3130
|
-
this.elements.forEach((element, type) => {
|
|
3131
|
-
if (type !== ELEMENT_TYPES.AUTH && type !== ELEMENT_TYPES.CHECKOUT) {
|
|
3132
|
-
element.sendMessage({ type: MESSAGE_TYPES.SET_TOKEN, token: message.token });
|
|
3133
|
-
}
|
|
3134
|
-
});
|
|
3135
|
-
break;
|
|
3136
|
-
case MESSAGE_TYPES.TOKEN_REFRESHED:
|
|
3137
|
-
this.accessToken = message.token;
|
|
3138
|
-
break;
|
|
3139
|
-
case MESSAGE_TYPES.ADDRESS_CHANGED:
|
|
3140
|
-
case MESSAGE_TYPES.ADDRESS_SELECTED:
|
|
3141
|
-
this.addressData = message.address;
|
|
3142
|
-
break;
|
|
3143
|
-
case MESSAGE_TYPES.PAYMENT_METHOD_SELECTED:
|
|
3144
|
-
this.paymentData = message.method;
|
|
3145
|
-
break;
|
|
3146
|
-
case MESSAGE_TYPES.CONTACT_PROVIDED:
|
|
3147
|
-
if (!this.customerData) {
|
|
3148
|
-
this.customerData = { name: "", email: null, phone: null };
|
|
3149
|
-
}
|
|
3150
|
-
if (message.contactType === "email") {
|
|
3151
|
-
this.customerData.email = message.contact;
|
|
3152
|
-
} else {
|
|
3153
|
-
this.customerData.phone = message.contact;
|
|
3154
|
-
}
|
|
3155
|
-
break;
|
|
3156
|
-
case MESSAGE_TYPES.LOGOUT_COMPLETE:
|
|
3157
|
-
if (this.checkoutInProgress && this.activeCheckoutAbort) {
|
|
3158
|
-
this.activeCheckoutAbort();
|
|
3159
|
-
}
|
|
3160
|
-
this.accessToken = null;
|
|
3161
|
-
this.accountId = null;
|
|
3162
|
-
this.customerId = null;
|
|
3163
|
-
this.customerData = null;
|
|
3164
|
-
this.addressData = null;
|
|
3165
|
-
this.paymentData = null;
|
|
3166
|
-
this.client.clearSession();
|
|
3167
|
-
break;
|
|
3168
|
-
}
|
|
3169
|
-
}
|
|
3170
|
-
_setAddressData(data) {
|
|
3171
|
-
this.addressData = data;
|
|
3172
|
-
}
|
|
3173
|
-
_setPaymentData(data) {
|
|
3174
|
-
this.paymentData = data;
|
|
3175
|
-
}
|
|
3176
|
-
_setGuestContact(contact, contactType) {
|
|
3177
|
-
if (!this.customerData) {
|
|
3178
|
-
this.customerData = { name: "", email: null, phone: null };
|
|
3179
|
-
}
|
|
3180
|
-
if (contactType === "email") {
|
|
3181
|
-
this.customerData.email = contact;
|
|
3182
|
-
} else {
|
|
3183
|
-
this.customerData.phone = contact;
|
|
3184
|
-
}
|
|
3185
|
-
}
|
|
3186
|
-
};
|
|
3187
|
-
var CimplifyElement = class {
|
|
3188
|
-
constructor(type, businessId, linkUrl, options, parent) {
|
|
3189
|
-
this.iframe = null;
|
|
3190
|
-
this.container = null;
|
|
3191
|
-
this.mounted = false;
|
|
3192
|
-
this.pendingInit = null;
|
|
3193
|
-
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
3194
|
-
this.resolvers = /* @__PURE__ */ new Map();
|
|
3195
|
-
this.listening = false;
|
|
3196
|
-
this.type = type;
|
|
3197
|
-
this.businessId = businessId;
|
|
3198
|
-
this.linkUrl = linkUrl;
|
|
3199
|
-
this.options = options;
|
|
3200
|
-
this.parent = parent;
|
|
3201
|
-
this.nonce = generateNonce();
|
|
3202
|
-
this.boundHandleMessage = this.handleMessage.bind(this);
|
|
3203
|
-
if (typeof window !== "undefined") {
|
|
3204
|
-
window.addEventListener("message", this.boundHandleMessage);
|
|
3205
|
-
this.listening = true;
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
mount(container) {
|
|
3209
|
-
if (this.mounted) {
|
|
3210
|
-
console.warn(`Element ${this.type} is already mounted`);
|
|
3211
|
-
return;
|
|
3212
|
-
}
|
|
3213
|
-
const target = typeof container === "string" ? document.querySelector(container) : container;
|
|
3214
|
-
if (!target) {
|
|
3215
|
-
console.error(`Container not found: ${container}`);
|
|
3216
|
-
return;
|
|
3217
|
-
}
|
|
3218
|
-
this.container = target;
|
|
3219
|
-
this.mounted = true;
|
|
3220
|
-
if (!this.listening && typeof window !== "undefined") {
|
|
3221
|
-
window.addEventListener("message", this.boundHandleMessage);
|
|
3222
|
-
this.listening = true;
|
|
3223
|
-
}
|
|
3224
|
-
void this.createIframe();
|
|
3225
|
-
}
|
|
3226
|
-
destroy() {
|
|
3227
|
-
if (this.iframe) {
|
|
3228
|
-
this.iframe.remove();
|
|
3229
|
-
this.iframe = null;
|
|
3230
|
-
}
|
|
3231
|
-
this.container = null;
|
|
3232
|
-
this.mounted = false;
|
|
3233
|
-
this.eventHandlers.clear();
|
|
3234
|
-
this.resolvers.forEach((entry) => clearTimeout(entry.timeoutId));
|
|
3235
|
-
this.resolvers.clear();
|
|
3236
|
-
if (this.listening && typeof window !== "undefined") {
|
|
3237
|
-
window.removeEventListener("message", this.boundHandleMessage);
|
|
3238
|
-
this.listening = false;
|
|
3239
|
-
}
|
|
3240
|
-
this.parent._removeElement(this.type, this);
|
|
3241
|
-
}
|
|
3242
|
-
on(event, handler) {
|
|
3243
|
-
if (!this.eventHandlers.has(event)) {
|
|
3244
|
-
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
3245
|
-
}
|
|
3246
|
-
this.eventHandlers.get(event).add(handler);
|
|
3247
|
-
}
|
|
3248
|
-
off(event, handler) {
|
|
3249
|
-
this.eventHandlers.get(event)?.delete(handler);
|
|
3250
|
-
}
|
|
3251
|
-
async getData() {
|
|
3252
|
-
if (!this.isMounted()) {
|
|
3253
|
-
return null;
|
|
3254
|
-
}
|
|
3255
|
-
return new Promise((resolve) => {
|
|
3256
|
-
const id = Math.random().toString(36).slice(2);
|
|
3257
|
-
const timeoutId = setTimeout(() => {
|
|
3258
|
-
const entry = this.resolvers.get(id);
|
|
3259
|
-
if (!entry) {
|
|
3260
|
-
return;
|
|
3261
|
-
}
|
|
3262
|
-
this.resolvers.delete(id);
|
|
3263
|
-
entry.resolve(null);
|
|
3264
|
-
}, 5e3);
|
|
3265
|
-
this.resolvers.set(id, { resolve, timeoutId });
|
|
3266
|
-
this.sendMessage({ type: MESSAGE_TYPES.GET_DATA });
|
|
3267
|
-
});
|
|
3268
|
-
}
|
|
3269
|
-
setCart(cart) {
|
|
3270
|
-
this.sendMessage({ type: MESSAGE_TYPES.SET_CART, cart });
|
|
3271
|
-
}
|
|
3272
|
-
sendMessage(message) {
|
|
3273
|
-
if (this.iframe?.contentWindow) {
|
|
3274
|
-
this.iframe.contentWindow.postMessage(message, this.linkUrl);
|
|
3275
|
-
} else {
|
|
3276
|
-
console.warn("[cimplify:checkout] sendMessage DROPPED \u2014 no contentWindow", { type: message.type, hasIframe: !!this.iframe, isConnected: this.iframe?.isConnected });
|
|
3277
|
-
}
|
|
3278
|
-
}
|
|
3279
|
-
getContentWindow() {
|
|
3280
|
-
return this.iframe?.contentWindow ?? null;
|
|
3281
|
-
}
|
|
3282
|
-
isMounted() {
|
|
3283
|
-
return this.mounted && Boolean(this.iframe) && this.iframe?.isConnected === true;
|
|
3284
|
-
}
|
|
3285
|
-
async createIframe() {
|
|
3286
|
-
if (!this.container) return;
|
|
3287
|
-
const resolvedBusinessId = this.businessId ?? await this.parent.resolveBusinessId();
|
|
3288
|
-
if (!resolvedBusinessId || !this.container || !this.mounted) {
|
|
3289
|
-
console.error("Unable to mount element without a resolved business ID");
|
|
3290
|
-
this.emit(EVENT_TYPES.ERROR, {
|
|
3291
|
-
code: "BUSINESS_ID_REQUIRED",
|
|
3292
|
-
message: "Unable to initialize checkout without a business ID."
|
|
3293
|
-
});
|
|
3294
|
-
return;
|
|
3295
|
-
}
|
|
3296
|
-
this.businessId = resolvedBusinessId;
|
|
3297
|
-
const iframe = document.createElement("iframe");
|
|
3298
|
-
const url = new URL(`${this.linkUrl}/elements/${this.type}`);
|
|
3299
|
-
url.searchParams.set("businessId", resolvedBusinessId);
|
|
3300
|
-
url.searchParams.set("nonce", this.nonce);
|
|
3301
|
-
if (this.options.prefillEmail) url.searchParams.set("email", this.options.prefillEmail);
|
|
3302
|
-
if (this.options.mode) url.searchParams.set("mode", this.options.mode);
|
|
3303
|
-
iframe.src = url.toString();
|
|
3304
|
-
iframe.style.border = "none";
|
|
3305
|
-
iframe.style.width = "100%";
|
|
3306
|
-
iframe.style.minHeight = "200px";
|
|
3307
|
-
iframe.style.display = "block";
|
|
3308
|
-
iframe.style.overflow = "hidden";
|
|
3309
|
-
iframe.style.background = "transparent";
|
|
3310
|
-
iframe.setAttribute("allowtransparency", "true");
|
|
3311
|
-
iframe.setAttribute("frameborder", "0");
|
|
3312
|
-
iframe.setAttribute("allow", "geolocation");
|
|
3313
|
-
iframe.setAttribute(
|
|
3314
|
-
"sandbox",
|
|
3315
|
-
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
3316
|
-
);
|
|
3317
|
-
this.iframe = iframe;
|
|
3318
|
-
this.container.appendChild(iframe);
|
|
3319
|
-
this.pendingInit = {
|
|
3320
|
-
businessId: resolvedBusinessId,
|
|
3321
|
-
publicKey: this.parent.getPublicKey()
|
|
3322
|
-
};
|
|
3323
|
-
}
|
|
3324
|
-
handleMessage(event) {
|
|
3325
|
-
if (!isAllowedOrigin(event.origin)) {
|
|
3326
|
-
return;
|
|
3327
|
-
}
|
|
3328
|
-
const data = event.data;
|
|
3329
|
-
if (data?.nonce) {
|
|
3330
|
-
if (data.nonce !== this.nonce) return;
|
|
3331
|
-
} else {
|
|
3332
|
-
const iframeWindow = this.iframe?.contentWindow;
|
|
3333
|
-
if (iframeWindow && event.source && event.source !== iframeWindow) {
|
|
3334
|
-
return;
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
const message = parseIframeMessage(event.data);
|
|
3338
|
-
if (!message) return;
|
|
3339
|
-
switch (message.type) {
|
|
3340
|
-
case MESSAGE_TYPES.READY:
|
|
3341
|
-
if (this.iframe && message.height) this.iframe.style.height = `${message.height}px`;
|
|
3342
|
-
if (this.pendingInit) {
|
|
3343
|
-
const { publicKey } = this.pendingInit;
|
|
3344
|
-
this.sendMessage({
|
|
3345
|
-
type: MESSAGE_TYPES.INIT,
|
|
3346
|
-
businessId: this.pendingInit.businessId,
|
|
3347
|
-
publicKey,
|
|
3348
|
-
demoMode: publicKey.length === 0,
|
|
3349
|
-
prefillEmail: this.options.prefillEmail,
|
|
3350
|
-
appearance: this.parent.getAppearance(),
|
|
3351
|
-
orderTypes: this.options.orderTypes,
|
|
3352
|
-
defaultOrderType: this.options.defaultOrderType,
|
|
3353
|
-
renderSubmitButton: true,
|
|
3354
|
-
submitLabel: this.options.submitLabel
|
|
3355
|
-
});
|
|
3356
|
-
const token = this.parent.getAccessToken();
|
|
3357
|
-
if (token && this.type !== ELEMENT_TYPES.AUTH) {
|
|
3358
|
-
this.sendMessage({ type: MESSAGE_TYPES.SET_TOKEN, token });
|
|
3359
|
-
}
|
|
3360
|
-
this.pendingInit = null;
|
|
3361
|
-
}
|
|
3362
|
-
this.emit(EVENT_TYPES.READY, { height: message.height });
|
|
3363
|
-
break;
|
|
3364
|
-
case MESSAGE_TYPES.HEIGHT_CHANGE:
|
|
3365
|
-
if (this.iframe) this.iframe.style.height = `${message.height}px`;
|
|
3366
|
-
break;
|
|
3367
|
-
case MESSAGE_TYPES.AUTHENTICATED:
|
|
3368
|
-
const customer = message.customer ?? {
|
|
3369
|
-
name: "",
|
|
3370
|
-
email: null,
|
|
3371
|
-
phone: null
|
|
3372
|
-
};
|
|
3373
|
-
this.emit(EVENT_TYPES.AUTHENTICATED, {
|
|
3374
|
-
accountId: message.accountId,
|
|
3375
|
-
customerId: message.customerId,
|
|
3376
|
-
token: message.token,
|
|
3377
|
-
customer
|
|
3378
|
-
});
|
|
3379
|
-
break;
|
|
3380
|
-
case MESSAGE_TYPES.REQUIRES_OTP:
|
|
3381
|
-
this.emit(EVENT_TYPES.REQUIRES_OTP, { contactMasked: message.contactMasked });
|
|
3382
|
-
break;
|
|
3383
|
-
case MESSAGE_TYPES.ERROR:
|
|
3384
|
-
this.emit(EVENT_TYPES.ERROR, { code: message.code, message: message.message });
|
|
3385
|
-
break;
|
|
3386
|
-
case MESSAGE_TYPES.ADDRESS_CHANGED:
|
|
3387
|
-
case MESSAGE_TYPES.ADDRESS_SELECTED:
|
|
3388
|
-
this.parent._setAddressData(message.address);
|
|
3389
|
-
this.emit(EVENT_TYPES.CHANGE, { address: message.address });
|
|
3390
|
-
this.resolveData(message.address);
|
|
3391
|
-
break;
|
|
3392
|
-
case MESSAGE_TYPES.PAYMENT_METHOD_SELECTED:
|
|
3393
|
-
this.parent._setPaymentData(message.method);
|
|
3394
|
-
this.emit(EVENT_TYPES.CHANGE, { paymentMethod: message.method });
|
|
3395
|
-
this.resolveData(message.method);
|
|
3396
|
-
break;
|
|
3397
|
-
case MESSAGE_TYPES.CONTACT_PROVIDED:
|
|
3398
|
-
this.parent._setGuestContact(message.contact, message.contactType);
|
|
3399
|
-
break;
|
|
3400
|
-
case MESSAGE_TYPES.ORDER_TYPE_CHANGED:
|
|
3401
|
-
this.emit(EVENT_TYPES.ORDER_TYPE_CHANGED, { orderType: message.orderType });
|
|
3402
|
-
break;
|
|
3403
|
-
case MESSAGE_TYPES.REQUEST_SUBMIT:
|
|
3404
|
-
this.emit(EVENT_TYPES.REQUEST_SUBMIT, {});
|
|
3405
|
-
break;
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
emit(event, data) {
|
|
3409
|
-
this.eventHandlers.get(event)?.forEach((handler) => handler(data));
|
|
3410
|
-
}
|
|
3411
|
-
resolveData(data) {
|
|
3412
|
-
this.resolvers.forEach((entry) => {
|
|
3413
|
-
clearTimeout(entry.timeoutId);
|
|
3414
|
-
entry.resolve(data);
|
|
3415
|
-
});
|
|
3416
|
-
this.resolvers.clear();
|
|
3417
|
-
}
|
|
3418
|
-
};
|
|
3419
|
-
function createElements(client, businessId, options) {
|
|
3420
|
-
return new CimplifyElements(client, businessId, options);
|
|
3421
|
-
}
|
|
3422
|
-
|
|
3423
|
-
// src/client.ts
|
|
3424
|
-
var ACCESS_TOKEN_STORAGE_KEY = "cimplify_access_token";
|
|
3425
|
-
var SESSION_TOKEN_STORAGE_KEY = "cimplify_session_token";
|
|
3426
|
-
var ORDER_TOKEN_PREFIX = "cimplify_ot_";
|
|
3427
|
-
var SESSION_TOKEN_HEADER = "x-session-token";
|
|
3428
|
-
var ORDER_TOKEN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3429
|
-
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
3430
|
-
var DEFAULT_MAX_RETRIES = 3;
|
|
3431
|
-
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
3432
|
-
function sleep(ms) {
|
|
3433
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3434
|
-
}
|
|
3435
|
-
function isRetryable(error) {
|
|
3436
|
-
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
3437
|
-
return true;
|
|
3438
|
-
}
|
|
3439
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
3440
|
-
return true;
|
|
3441
|
-
}
|
|
3442
|
-
if (error instanceof CimplifyError) {
|
|
3443
|
-
return error.retryable;
|
|
3444
|
-
}
|
|
3445
|
-
return false;
|
|
3446
|
-
}
|
|
3447
|
-
function toNetworkError(error, isTestMode) {
|
|
3448
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
3449
|
-
return enrichError(
|
|
3450
|
-
new CimplifyError(
|
|
3451
|
-
ErrorCode.TIMEOUT,
|
|
3452
|
-
"Request timed out. Please check your connection and try again.",
|
|
3453
|
-
true
|
|
3454
|
-
),
|
|
3455
|
-
{ isTestMode }
|
|
3456
|
-
);
|
|
3457
|
-
}
|
|
3458
|
-
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
3459
|
-
return enrichError(
|
|
3460
|
-
new CimplifyError(
|
|
3461
|
-
ErrorCode.NETWORK_ERROR,
|
|
3462
|
-
"Network error. Please check your internet connection.",
|
|
3463
|
-
true
|
|
3464
|
-
),
|
|
3465
|
-
{ isTestMode }
|
|
3466
|
-
);
|
|
3467
|
-
}
|
|
3468
|
-
if (error instanceof CimplifyError) {
|
|
3469
|
-
return enrichError(error, { isTestMode });
|
|
3470
|
-
}
|
|
3471
|
-
return enrichError(
|
|
3472
|
-
new CimplifyError(
|
|
3473
|
-
ErrorCode.UNKNOWN_ERROR,
|
|
3474
|
-
error instanceof Error ? error.message : "An unknown error occurred",
|
|
3475
|
-
false
|
|
3476
|
-
),
|
|
3477
|
-
{ isTestMode }
|
|
3478
|
-
);
|
|
3479
|
-
}
|
|
3480
|
-
function deriveUrls() {
|
|
3481
|
-
const hostname = typeof window !== "undefined" ? window.location.hostname : "";
|
|
3482
|
-
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
3483
|
-
const protocol = window.location.protocol;
|
|
3484
|
-
return {
|
|
3485
|
-
baseUrl: `${protocol}//${hostname}:8082`,
|
|
3486
|
-
linkApiUrl: `${protocol}//${hostname}:8080`
|
|
3487
|
-
};
|
|
3488
|
-
}
|
|
3489
|
-
return {
|
|
3490
|
-
baseUrl: "https://storefronts.cimplify.io",
|
|
3491
|
-
linkApiUrl: "https://api.cimplify.io"
|
|
3492
|
-
};
|
|
3493
|
-
}
|
|
3494
|
-
function normalizeUrl(url) {
|
|
3495
|
-
const trimmed = url.trim();
|
|
3496
|
-
if (!trimmed) return trimmed;
|
|
3497
|
-
return trimmed.replace(/\/+$/, "");
|
|
3498
|
-
}
|
|
3499
|
-
function getEnvPublicKey() {
|
|
3500
|
-
try {
|
|
3501
|
-
const env = globalThis.process;
|
|
3502
|
-
return env?.env.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY || void 0;
|
|
3503
|
-
} catch {
|
|
3504
|
-
return void 0;
|
|
3505
|
-
}
|
|
3506
|
-
}
|
|
3507
|
-
var CimplifyClient = class {
|
|
3508
|
-
constructor(config = {}) {
|
|
3509
|
-
this.accessToken = null;
|
|
3510
|
-
this.sessionToken = null;
|
|
3511
|
-
this.context = {};
|
|
3512
|
-
this.businessId = null;
|
|
3513
|
-
this.businessIdResolvePromise = null;
|
|
3514
|
-
this.inflightRequests = /* @__PURE__ */ new Map();
|
|
3515
|
-
this.publicKey = config.publicKey || getEnvPublicKey() || "";
|
|
3516
|
-
const urls = deriveUrls();
|
|
3517
|
-
this.baseUrl = config.baseUrl ? normalizeUrl(config.baseUrl) : urls.baseUrl;
|
|
3518
|
-
this.linkApiUrl = config.linkApiUrl ? normalizeUrl(config.linkApiUrl) : urls.linkApiUrl;
|
|
3519
|
-
this.credentials = config.credentials || "include";
|
|
3520
|
-
this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
3521
|
-
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
3522
|
-
this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
|
|
3523
|
-
this.hooks = config.hooks ?? {};
|
|
3524
|
-
this.accessToken = this.loadAccessToken();
|
|
3525
|
-
this.sessionToken = this.loadSessionToken();
|
|
3526
|
-
if (!this.publicKey && !config.suppressPublicKeyWarning) {
|
|
3527
|
-
console.warn(
|
|
3528
|
-
'[Cimplify] No public key found. Set NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY in your environment, or pass { publicKey: "pk_..." } to createCimplifyClient().'
|
|
3529
|
-
);
|
|
3530
|
-
}
|
|
3531
|
-
}
|
|
3532
|
-
buildUrl(base, path) {
|
|
3533
|
-
const url = `${base}${path}`;
|
|
3534
|
-
const locationId = this.context.location_id;
|
|
3535
|
-
if (!locationId) return url;
|
|
3536
|
-
const separator = url.includes("?") ? "&" : "?";
|
|
3537
|
-
return `${url}${separator}location_id=${encodeURIComponent(locationId)}`;
|
|
3538
|
-
}
|
|
3539
|
-
getAccessToken() {
|
|
3540
|
-
return this.accessToken;
|
|
3541
|
-
}
|
|
3542
|
-
getPublicKey() {
|
|
3543
|
-
return this.publicKey;
|
|
3544
|
-
}
|
|
3545
|
-
isTestMode() {
|
|
3546
|
-
return this.publicKey.trim().startsWith("pk_test_");
|
|
3547
|
-
}
|
|
3548
|
-
setAccessToken(token) {
|
|
3549
|
-
const previous = this.accessToken;
|
|
3550
|
-
this.accessToken = token;
|
|
3551
|
-
this.saveAccessToken(token);
|
|
3552
|
-
this.hooks.onSessionChange?.({
|
|
3553
|
-
previousToken: previous,
|
|
3554
|
-
newToken: token,
|
|
3555
|
-
source: "manual"
|
|
3556
|
-
});
|
|
3557
|
-
}
|
|
3558
|
-
clearSession() {
|
|
3559
|
-
const previous = this.accessToken;
|
|
3560
|
-
this.accessToken = null;
|
|
3561
|
-
this.sessionToken = null;
|
|
3562
|
-
this.saveAccessToken(null);
|
|
3563
|
-
if (typeof window !== "undefined" && window.localStorage) {
|
|
3564
|
-
localStorage.removeItem(SESSION_TOKEN_STORAGE_KEY);
|
|
3565
|
-
}
|
|
3566
|
-
this.hooks.onSessionChange?.({
|
|
3567
|
-
previousToken: previous,
|
|
3568
|
-
newToken: null,
|
|
3569
|
-
source: "clear"
|
|
3570
|
-
});
|
|
3571
|
-
}
|
|
3572
|
-
setOrderToken(orderId, token) {
|
|
3573
|
-
if (typeof window !== "undefined" && window.localStorage) {
|
|
3574
|
-
try {
|
|
3575
|
-
const entry = JSON.stringify({ token, storedAt: Date.now() });
|
|
3576
|
-
localStorage.setItem(`${ORDER_TOKEN_PREFIX}${orderId}`, entry);
|
|
3577
|
-
} catch {
|
|
3578
|
-
}
|
|
3579
|
-
}
|
|
3580
|
-
}
|
|
3581
|
-
getOrderToken(orderId) {
|
|
3582
|
-
if (typeof window !== "undefined" && window.localStorage) {
|
|
3583
|
-
try {
|
|
3584
|
-
const raw = localStorage.getItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
|
|
3585
|
-
if (!raw) return null;
|
|
3586
|
-
const entry = JSON.parse(raw);
|
|
3587
|
-
if (Date.now() - entry.storedAt > ORDER_TOKEN_TTL_MS) {
|
|
3588
|
-
localStorage.removeItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
|
|
3589
|
-
return null;
|
|
3590
|
-
}
|
|
3591
|
-
return entry.token;
|
|
3592
|
-
} catch {
|
|
3593
|
-
return null;
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
return null;
|
|
3597
|
-
}
|
|
3598
|
-
clearOrderTokens() {
|
|
3599
|
-
if (typeof window !== "undefined" && window.localStorage) {
|
|
3600
|
-
const keysToRemove = [];
|
|
3601
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
3602
|
-
const key = localStorage.key(i);
|
|
3603
|
-
if (key?.startsWith(ORDER_TOKEN_PREFIX)) {
|
|
3604
|
-
keysToRemove.push(key);
|
|
3605
|
-
}
|
|
3606
|
-
}
|
|
3607
|
-
keysToRemove.forEach((k) => localStorage.removeItem(k));
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
setLocationId(locationId) {
|
|
3611
|
-
if (locationId) {
|
|
3612
|
-
this.context.location_id = locationId;
|
|
3613
|
-
} else {
|
|
3614
|
-
delete this.context.location_id;
|
|
3615
|
-
}
|
|
3616
|
-
}
|
|
3617
|
-
getLocationId() {
|
|
3618
|
-
return this.context.location_id ?? null;
|
|
3619
|
-
}
|
|
3620
|
-
setBusinessId(businessId) {
|
|
3621
|
-
const normalized = businessId.trim();
|
|
3622
|
-
if (!normalized) {
|
|
3623
|
-
return;
|
|
3624
|
-
}
|
|
3625
|
-
this.businessId = normalized;
|
|
3626
|
-
}
|
|
3627
|
-
getBusinessId() {
|
|
3628
|
-
return this.businessId;
|
|
3629
|
-
}
|
|
3630
|
-
async resolveBusinessId() {
|
|
3631
|
-
if (this.businessId) {
|
|
3632
|
-
return this.businessId;
|
|
3633
|
-
}
|
|
3634
|
-
if (this.businessIdResolvePromise) {
|
|
3635
|
-
return this.businessIdResolvePromise;
|
|
3636
|
-
}
|
|
3637
|
-
this.businessIdResolvePromise = (async () => {
|
|
3638
|
-
const result = await this.business.getInfo();
|
|
3639
|
-
if (!result.ok || !result.value?.id) {
|
|
3640
|
-
throw new CimplifyError(
|
|
3641
|
-
ErrorCode.NOT_FOUND,
|
|
3642
|
-
"Unable to resolve business ID from the current public key.",
|
|
3643
|
-
true
|
|
3644
|
-
);
|
|
3645
|
-
}
|
|
3646
|
-
this.businessId = result.value.id;
|
|
3647
|
-
return result.value.id;
|
|
3648
|
-
})();
|
|
3649
|
-
try {
|
|
3650
|
-
return await this.businessIdResolvePromise;
|
|
3651
|
-
} finally {
|
|
3652
|
-
this.businessIdResolvePromise = null;
|
|
3653
|
-
}
|
|
3654
|
-
}
|
|
3655
|
-
loadAccessToken() {
|
|
3656
|
-
if (typeof window !== "undefined" && window.localStorage) {
|
|
3657
|
-
return localStorage.getItem(ACCESS_TOKEN_STORAGE_KEY);
|
|
3658
|
-
}
|
|
3659
|
-
return null;
|
|
3660
|
-
}
|
|
3661
|
-
saveAccessToken(token) {
|
|
3662
|
-
if (typeof window !== "undefined" && window.localStorage) {
|
|
3663
|
-
try {
|
|
3664
|
-
if (token) {
|
|
3665
|
-
localStorage.setItem(ACCESS_TOKEN_STORAGE_KEY, token);
|
|
3666
|
-
} else {
|
|
3667
|
-
localStorage.removeItem(ACCESS_TOKEN_STORAGE_KEY);
|
|
3668
|
-
}
|
|
3669
|
-
} catch {
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
}
|
|
3673
|
-
loadSessionToken() {
|
|
3674
|
-
if (typeof window !== "undefined" && window.localStorage) {
|
|
3675
|
-
return localStorage.getItem(SESSION_TOKEN_STORAGE_KEY);
|
|
3676
|
-
}
|
|
3677
|
-
return null;
|
|
3678
|
-
}
|
|
3679
|
-
saveSessionToken(token) {
|
|
3680
|
-
if (typeof window !== "undefined" && window.localStorage) {
|
|
3681
|
-
try {
|
|
3682
|
-
localStorage.setItem(SESSION_TOKEN_STORAGE_KEY, token);
|
|
3683
|
-
} catch {
|
|
3684
|
-
}
|
|
3685
|
-
}
|
|
3686
|
-
}
|
|
3687
|
-
captureSessionToken(response) {
|
|
3688
|
-
const token = response.headers.get(SESSION_TOKEN_HEADER);
|
|
3689
|
-
if (token && token !== this.sessionToken) {
|
|
3690
|
-
this.sessionToken = token;
|
|
3691
|
-
this.saveSessionToken(token);
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
getHeaders() {
|
|
3695
|
-
const headers = {
|
|
3696
|
-
"Content-Type": "application/json"
|
|
3697
|
-
};
|
|
3698
|
-
const trimmedPublicKey = this.publicKey.trim();
|
|
3699
|
-
if (trimmedPublicKey) {
|
|
3700
|
-
headers["X-API-Key"] = trimmedPublicKey;
|
|
3701
|
-
}
|
|
3702
|
-
if (this.accessToken) {
|
|
3703
|
-
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
3704
|
-
}
|
|
3705
|
-
if (this.sessionToken) {
|
|
3706
|
-
headers[SESSION_TOKEN_HEADER] = this.sessionToken;
|
|
3707
|
-
}
|
|
3708
|
-
return headers;
|
|
3709
|
-
}
|
|
3710
|
-
async resilientFetch(url, options) {
|
|
3711
|
-
const method = options.method || "GET";
|
|
3712
|
-
const path = url.replace(this.baseUrl, "").replace(this.linkApiUrl, "");
|
|
3713
|
-
const startTime = Date.now();
|
|
3714
|
-
let retryCount = 0;
|
|
3715
|
-
const context = {
|
|
3716
|
-
method,
|
|
3717
|
-
path,
|
|
3718
|
-
url,
|
|
3719
|
-
body: options.body ? JSON.parse(options.body) : void 0,
|
|
3720
|
-
startTime
|
|
3721
|
-
};
|
|
3722
|
-
this.hooks.onRequestStart?.(context);
|
|
3723
|
-
let lastError;
|
|
3724
|
-
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
3725
|
-
try {
|
|
3726
|
-
const controller = new AbortController();
|
|
3727
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3728
|
-
const response = await fetch(url, {
|
|
3729
|
-
...options,
|
|
3730
|
-
signal: controller.signal
|
|
3731
|
-
});
|
|
3732
|
-
clearTimeout(timeoutId);
|
|
3733
|
-
this.captureSessionToken(response);
|
|
3734
|
-
if (response.ok) {
|
|
3735
|
-
this.hooks.onRequestSuccess?.({
|
|
3736
|
-
...context,
|
|
3737
|
-
status: response.status,
|
|
3738
|
-
durationMs: Date.now() - startTime
|
|
3739
|
-
});
|
|
3740
|
-
return response;
|
|
3741
|
-
}
|
|
3742
|
-
if (response.status === 429 && attempt < this.maxRetries) {
|
|
3743
|
-
retryCount++;
|
|
3744
|
-
const retryAfter = response.headers.get("Retry-After");
|
|
3745
|
-
const delay = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3, 3e4) || this.retryDelay * Math.pow(2, attempt) : this.retryDelay * Math.pow(2, attempt);
|
|
3746
|
-
this.hooks.onRetry?.({
|
|
3747
|
-
...context,
|
|
3748
|
-
attempt: retryCount,
|
|
3749
|
-
delayMs: delay,
|
|
3750
|
-
error: new Error(`Rate limited: ${response.status}`)
|
|
3751
|
-
});
|
|
3752
|
-
await sleep(delay);
|
|
3753
|
-
continue;
|
|
3754
|
-
}
|
|
3755
|
-
if (response.status >= 400 && response.status < 500) {
|
|
3756
|
-
this.hooks.onRequestError?.({
|
|
3757
|
-
...context,
|
|
3758
|
-
error: new CimplifyError(
|
|
3759
|
-
`HTTP_${response.status}`,
|
|
3760
|
-
`Request failed with status ${response.status}`,
|
|
3761
|
-
false
|
|
3762
|
-
),
|
|
3763
|
-
status: response.status,
|
|
3764
|
-
durationMs: Date.now() - startTime,
|
|
3765
|
-
retryCount,
|
|
3766
|
-
retryable: false
|
|
3767
|
-
});
|
|
3768
|
-
return response;
|
|
3769
|
-
}
|
|
3770
|
-
if (response.status >= 500 && attempt < this.maxRetries) {
|
|
3771
|
-
retryCount++;
|
|
3772
|
-
const delay = this.retryDelay * Math.pow(2, attempt);
|
|
3773
|
-
this.hooks.onRetry?.({
|
|
3774
|
-
...context,
|
|
3775
|
-
attempt: retryCount,
|
|
3776
|
-
delayMs: delay,
|
|
3777
|
-
error: new Error(`Server error: ${response.status}`)
|
|
3778
|
-
});
|
|
3779
|
-
await sleep(delay);
|
|
3780
|
-
continue;
|
|
3781
|
-
}
|
|
3782
|
-
this.hooks.onRequestError?.({
|
|
3783
|
-
...context,
|
|
3784
|
-
error: new CimplifyError(
|
|
3785
|
-
"SERVER_ERROR",
|
|
3786
|
-
`Server error ${response.status} after ${retryCount} retries`,
|
|
3787
|
-
false
|
|
3788
|
-
),
|
|
3789
|
-
status: response.status,
|
|
3790
|
-
durationMs: Date.now() - startTime,
|
|
3791
|
-
retryCount,
|
|
3792
|
-
retryable: false
|
|
3793
|
-
});
|
|
3794
|
-
return response;
|
|
3795
|
-
} catch (error) {
|
|
3796
|
-
lastError = error;
|
|
3797
|
-
const networkError = toNetworkError(error, this.isTestMode());
|
|
3798
|
-
const errorRetryable = isRetryable(error);
|
|
3799
|
-
if (!errorRetryable || attempt >= this.maxRetries) {
|
|
3800
|
-
this.hooks.onRequestError?.({
|
|
3801
|
-
...context,
|
|
3802
|
-
error: networkError,
|
|
3803
|
-
durationMs: Date.now() - startTime,
|
|
3804
|
-
retryCount,
|
|
3805
|
-
retryable: errorRetryable
|
|
3806
|
-
});
|
|
3807
|
-
throw networkError;
|
|
3808
|
-
}
|
|
3809
|
-
retryCount++;
|
|
3810
|
-
const delay = this.retryDelay * Math.pow(2, attempt);
|
|
3811
|
-
this.hooks.onRetry?.({
|
|
3812
|
-
...context,
|
|
3813
|
-
attempt: retryCount,
|
|
3814
|
-
delayMs: delay,
|
|
3815
|
-
error: networkError
|
|
3816
|
-
});
|
|
3817
|
-
await sleep(delay);
|
|
3818
|
-
}
|
|
3819
|
-
}
|
|
3820
|
-
const finalError = toNetworkError(lastError, this.isTestMode());
|
|
3821
|
-
this.hooks.onRequestError?.({
|
|
3822
|
-
...context,
|
|
3823
|
-
error: finalError,
|
|
3824
|
-
durationMs: Date.now() - startTime,
|
|
3825
|
-
retryCount,
|
|
3826
|
-
retryable: false
|
|
3827
|
-
});
|
|
3828
|
-
throw finalError;
|
|
3829
|
-
}
|
|
3830
|
-
getDedupeKey(type, payload) {
|
|
3831
|
-
return `${type}:${JSON.stringify(payload)}`;
|
|
3832
|
-
}
|
|
3833
|
-
async deduplicatedRequest(key, requestFn) {
|
|
3834
|
-
const existing = this.inflightRequests.get(key);
|
|
3835
|
-
if (existing) {
|
|
3836
|
-
return existing;
|
|
282
|
+
},
|
|
283
|
+
{ threshold: 0.5 }
|
|
284
|
+
);
|
|
285
|
+
if (containerRef.current) {
|
|
286
|
+
observer.observe(containerRef.current);
|
|
3837
287
|
}
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
const response = await this.resilientFetch(this.buildUrl(this.baseUrl, path), {
|
|
3848
|
-
method: "GET",
|
|
3849
|
-
credentials: this.credentials,
|
|
3850
|
-
headers: this.getHeaders()
|
|
3851
|
-
});
|
|
3852
|
-
return this.handleRestResponse(response);
|
|
3853
|
-
});
|
|
3854
|
-
}
|
|
3855
|
-
async post(path, body) {
|
|
3856
|
-
const response = await this.resilientFetch(this.buildUrl(this.baseUrl, path), {
|
|
3857
|
-
method: "POST",
|
|
3858
|
-
credentials: this.credentials,
|
|
3859
|
-
headers: this.getHeaders(),
|
|
3860
|
-
body: body ? JSON.stringify(body) : void 0
|
|
3861
|
-
});
|
|
3862
|
-
return this.handleRestResponse(response);
|
|
3863
|
-
}
|
|
3864
|
-
async patch(path, body) {
|
|
3865
|
-
const response = await this.resilientFetch(this.buildUrl(this.baseUrl, path), {
|
|
3866
|
-
method: "PATCH",
|
|
3867
|
-
credentials: this.credentials,
|
|
3868
|
-
headers: this.getHeaders(),
|
|
3869
|
-
body: body ? JSON.stringify(body) : void 0
|
|
3870
|
-
});
|
|
3871
|
-
return this.handleRestResponse(response);
|
|
3872
|
-
}
|
|
3873
|
-
async delete(path) {
|
|
3874
|
-
const response = await this.resilientFetch(this.buildUrl(this.baseUrl, path), {
|
|
3875
|
-
method: "DELETE",
|
|
3876
|
-
credentials: this.credentials,
|
|
3877
|
-
headers: this.getHeaders()
|
|
3878
|
-
});
|
|
3879
|
-
return this.handleRestResponse(response);
|
|
3880
|
-
}
|
|
3881
|
-
async linkGet(path) {
|
|
3882
|
-
const key = this.getDedupeKey("linkGet", path);
|
|
3883
|
-
return this.deduplicatedRequest(key, async () => {
|
|
3884
|
-
const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
|
|
3885
|
-
method: "GET",
|
|
3886
|
-
credentials: this.credentials,
|
|
3887
|
-
headers: this.getHeaders()
|
|
3888
|
-
});
|
|
3889
|
-
return this.handleRestResponse(response);
|
|
3890
|
-
});
|
|
3891
|
-
}
|
|
3892
|
-
async linkPost(path, body) {
|
|
3893
|
-
const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
|
|
3894
|
-
method: "POST",
|
|
3895
|
-
credentials: this.credentials,
|
|
3896
|
-
headers: this.getHeaders(),
|
|
3897
|
-
body: body ? JSON.stringify(body) : void 0
|
|
3898
|
-
});
|
|
3899
|
-
return this.handleRestResponse(response);
|
|
3900
|
-
}
|
|
3901
|
-
async linkDelete(path) {
|
|
3902
|
-
const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
|
|
3903
|
-
method: "DELETE",
|
|
3904
|
-
credentials: this.credentials,
|
|
3905
|
-
headers: this.getHeaders()
|
|
3906
|
-
});
|
|
3907
|
-
return this.handleRestResponse(response);
|
|
3908
|
-
}
|
|
3909
|
-
async handleRestResponse(response) {
|
|
3910
|
-
const json = await response.json();
|
|
3911
|
-
if (!response.ok) {
|
|
3912
|
-
const errorCode = typeof json.error === "object" && json.error?.error_code || typeof json.error === "object" && json.error?.code || "API_ERROR";
|
|
3913
|
-
const errorMessage = typeof json.error === "object" && json.error?.error_message || typeof json.error === "object" && json.error?.message || typeof json.error === "string" && json.error || typeof json.message === "string" && json.message || "An error occurred";
|
|
3914
|
-
const retryable = typeof json.error === "object" && typeof json.error?.retryable === "boolean" ? json.error.retryable : false;
|
|
3915
|
-
const error = enrichError(
|
|
3916
|
-
new CimplifyError(
|
|
3917
|
-
errorCode,
|
|
3918
|
-
errorMessage,
|
|
3919
|
-
retryable
|
|
3920
|
-
),
|
|
3921
|
-
{ isTestMode: this.isTestMode() }
|
|
3922
|
-
);
|
|
3923
|
-
if (response.status === 401 || error.code === ErrorCode.UNAUTHORIZED) {
|
|
3924
|
-
console.warn(
|
|
3925
|
-
"[Cimplify] Received 401 Unauthorized. Access token may be missing/expired. Refresh authentication and retry the request."
|
|
3926
|
-
);
|
|
288
|
+
return () => observer.disconnect();
|
|
289
|
+
}, [ad, siteId, slot, onImpression, identity, apiBase]);
|
|
290
|
+
if (isLoading) {
|
|
291
|
+
return /* @__PURE__ */ jsx(
|
|
292
|
+
"div",
|
|
293
|
+
{
|
|
294
|
+
className: `cimplify-ad cimplify-ad--loading ${className || ""}`,
|
|
295
|
+
style,
|
|
296
|
+
"aria-hidden": "true"
|
|
3927
297
|
}
|
|
3928
|
-
|
|
3929
|
-
}
|
|
3930
|
-
if (json?.success === false || json?.error?.code || json?.error?.message) {
|
|
3931
|
-
const error = enrichError(
|
|
3932
|
-
new CimplifyError(
|
|
3933
|
-
json.error?.code || "API_ERROR",
|
|
3934
|
-
json.error?.message || "An error occurred",
|
|
3935
|
-
json.error?.retryable || false
|
|
3936
|
-
),
|
|
3937
|
-
{ isTestMode: this.isTestMode() }
|
|
3938
|
-
);
|
|
3939
|
-
throw error;
|
|
3940
|
-
}
|
|
3941
|
-
if (json?.data !== void 0) {
|
|
3942
|
-
return json.data;
|
|
3943
|
-
}
|
|
3944
|
-
return json;
|
|
3945
|
-
}
|
|
3946
|
-
get catalogue() {
|
|
3947
|
-
if (!this._catalogue) {
|
|
3948
|
-
this._catalogue = new CatalogueQueries(this);
|
|
3949
|
-
}
|
|
3950
|
-
return this._catalogue;
|
|
3951
|
-
}
|
|
3952
|
-
get cart() {
|
|
3953
|
-
if (!this._cart) {
|
|
3954
|
-
this._cart = new CartOperations(this);
|
|
3955
|
-
}
|
|
3956
|
-
return this._cart;
|
|
3957
|
-
}
|
|
3958
|
-
get checkout() {
|
|
3959
|
-
if (!this._checkout) {
|
|
3960
|
-
this._checkout = new CheckoutService(this);
|
|
3961
|
-
}
|
|
3962
|
-
return this._checkout;
|
|
3963
|
-
}
|
|
3964
|
-
get orders() {
|
|
3965
|
-
if (!this._orders) {
|
|
3966
|
-
this._orders = new OrderQueries(this);
|
|
3967
|
-
}
|
|
3968
|
-
return this._orders;
|
|
3969
|
-
}
|
|
3970
|
-
get link() {
|
|
3971
|
-
if (!this._link) {
|
|
3972
|
-
this._link = new LinkService(this);
|
|
3973
|
-
}
|
|
3974
|
-
return this._link;
|
|
3975
|
-
}
|
|
3976
|
-
get auth() {
|
|
3977
|
-
if (!this._auth) {
|
|
3978
|
-
this._auth = new AuthService(this);
|
|
3979
|
-
}
|
|
3980
|
-
return this._auth;
|
|
3981
|
-
}
|
|
3982
|
-
get business() {
|
|
3983
|
-
if (!this._business) {
|
|
3984
|
-
this._business = new BusinessService(this);
|
|
3985
|
-
}
|
|
3986
|
-
return this._business;
|
|
3987
|
-
}
|
|
3988
|
-
get inventory() {
|
|
3989
|
-
if (!this._inventory) {
|
|
3990
|
-
this._inventory = new InventoryService(this);
|
|
3991
|
-
}
|
|
3992
|
-
return this._inventory;
|
|
3993
|
-
}
|
|
3994
|
-
get scheduling() {
|
|
3995
|
-
if (!this._scheduling) {
|
|
3996
|
-
this._scheduling = new SchedulingService(this);
|
|
3997
|
-
}
|
|
3998
|
-
return this._scheduling;
|
|
298
|
+
);
|
|
3999
299
|
}
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
300
|
+
if (!config?.enabled) return null;
|
|
301
|
+
if (error) return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
|
|
302
|
+
if (!ad) return null;
|
|
303
|
+
const handleClick = () => {
|
|
304
|
+
onClick?.(ad.id);
|
|
305
|
+
};
|
|
306
|
+
const positionStyles = position === "fixed" ? { position: "fixed", bottom: 0, left: 0, right: 0, zIndex: 9999 } : position === "sticky" ? { position: "sticky", top: 20 } : {};
|
|
307
|
+
return /* @__PURE__ */ jsx(
|
|
308
|
+
"div",
|
|
309
|
+
{
|
|
310
|
+
ref: containerRef,
|
|
311
|
+
id: `cimplify-ad-${ad.id}`,
|
|
312
|
+
className: `cimplify-ad cimplify-ad--${slot} cimplify-ad--${position} cimplify-ad--${config.theme} ${className || ""}`,
|
|
313
|
+
style: { ...positionStyles, ...style },
|
|
314
|
+
onClick: handleClick,
|
|
315
|
+
dangerouslySetInnerHTML: { __html: ad.html }
|
|
4003
316
|
}
|
|
4004
|
-
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/utils/cart-transform.ts
|
|
321
|
+
function getSelections(item) {
|
|
322
|
+
if (item.bundle_resolved?.selections?.length) {
|
|
323
|
+
return item.bundle_resolved.selections.map((s) => ({
|
|
324
|
+
name: s.product_name || s.component_id,
|
|
325
|
+
quantity: s.quantity,
|
|
326
|
+
variant_name: s.variant_name
|
|
327
|
+
}));
|
|
4005
328
|
}
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
329
|
+
if (item.composite_resolved?.selections?.length) {
|
|
330
|
+
return item.composite_resolved.selections.map((s) => ({
|
|
331
|
+
name: s.component_name || s.component_id,
|
|
332
|
+
quantity: s.quantity
|
|
333
|
+
}));
|
|
4011
334
|
}
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
335
|
+
return void 0;
|
|
336
|
+
}
|
|
337
|
+
function mapItem(item, fx) {
|
|
338
|
+
const amt = (value) => fx ? String(fx.convertPrice(value)) : String(value);
|
|
339
|
+
const result = {
|
|
340
|
+
name: item.name,
|
|
341
|
+
quantity: item.quantity,
|
|
342
|
+
unit_price: amt(item.base_price),
|
|
343
|
+
total_price: amt(item.total_price),
|
|
344
|
+
line_type: item.line_type
|
|
345
|
+
};
|
|
346
|
+
if (item.image_url) result.image_url = item.image_url;
|
|
347
|
+
const variantName = item.variant_info?.name || item.variant_name || void 0;
|
|
348
|
+
if (variantName) result.variant_name = variantName;
|
|
349
|
+
if (item.scheduled_start) result.scheduled_start = item.scheduled_start;
|
|
350
|
+
if (item.scheduled_end) result.scheduled_end = item.scheduled_end;
|
|
351
|
+
const selections = getSelections(item);
|
|
352
|
+
if (selections) result.selections = selections;
|
|
353
|
+
if (item.add_on_options?.length) {
|
|
354
|
+
result.add_ons = item.add_on_options.map((opt) => ({
|
|
355
|
+
name: opt.name,
|
|
356
|
+
price: amt(opt.price ?? "0")
|
|
357
|
+
}));
|
|
4017
358
|
}
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
this.setBusinessId(businessId);
|
|
4021
|
-
}
|
|
4022
|
-
return createElements(this, businessId ?? this.businessId ?? void 0, options);
|
|
359
|
+
if (item.special_instructions) {
|
|
360
|
+
result.special_instructions = item.special_instructions;
|
|
4023
361
|
}
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
function transformToCheckoutCart(cart, fx) {
|
|
365
|
+
const amt = (value) => fx ? String(fx.convertPrice(value)) : String(value);
|
|
366
|
+
return {
|
|
367
|
+
items: cart.items.map((item) => mapItem(item, fx)),
|
|
368
|
+
subtotal: amt(cart.pricing.subtotal),
|
|
369
|
+
tax_amount: amt(cart.pricing.tax_amount),
|
|
370
|
+
total_discounts: amt(cart.pricing.total_discounts),
|
|
371
|
+
service_charge: amt(cart.pricing.service_charge),
|
|
372
|
+
total: amt(cart.pricing.total_price),
|
|
373
|
+
currency: fx?.displayCurrency ?? cart.pricing.currency
|
|
374
|
+
};
|
|
4027
375
|
}
|
|
4028
376
|
var CART_STORAGE_PREFIX = "cimplify:cart:v2";
|
|
4029
377
|
var DEFAULT_TAX_RATE = 0.08875;
|
|
@@ -4114,6 +462,28 @@ function calculateSummary(items) {
|
|
|
4114
462
|
total: roundMoney(subtotal + tax)
|
|
4115
463
|
};
|
|
4116
464
|
}
|
|
465
|
+
function lineKeyFromItem(item) {
|
|
466
|
+
return buildLineKey(item.product.id, {
|
|
467
|
+
locationId: item.locationId,
|
|
468
|
+
variantId: item.variantId,
|
|
469
|
+
quoteId: item.quoteId,
|
|
470
|
+
addOnOptionIds: item.addOnOptionIds,
|
|
471
|
+
bundleSelections: item.bundleSelections,
|
|
472
|
+
compositeSelections: item.compositeSelections,
|
|
473
|
+
scheduledStart: item.scheduledStart,
|
|
474
|
+
staffId: item.staffId
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function attachInternalItemMetadata(item) {
|
|
478
|
+
return {
|
|
479
|
+
...item,
|
|
480
|
+
_lineKey: lineKeyFromItem(item)
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function stripInternalItemMetadata(item) {
|
|
484
|
+
const { _lineKey, ...rest } = item;
|
|
485
|
+
return rest;
|
|
486
|
+
}
|
|
4117
487
|
function toProductFromServerItem(item, businessId) {
|
|
4118
488
|
return {
|
|
4119
489
|
id: item.item_id,
|
|
@@ -4143,7 +513,7 @@ function mapServerCart(serverCart) {
|
|
|
4143
513
|
price_adjustment: item.variant_info?.price_adjustment
|
|
4144
514
|
} : void 0;
|
|
4145
515
|
const quoteId = typeof item.metadata?.quote_id === "string" ? item.metadata.quote_id : void 0;
|
|
4146
|
-
return {
|
|
516
|
+
return attachInternalItemMetadata({
|
|
4147
517
|
id: item.id,
|
|
4148
518
|
product: toProductFromServerItem(item, serverCart.business_id),
|
|
4149
519
|
quantity: clampQuantity(item.quantity),
|
|
@@ -4151,7 +521,7 @@ function mapServerCart(serverCart) {
|
|
|
4151
521
|
variantId: item.variant_id,
|
|
4152
522
|
variant,
|
|
4153
523
|
quoteId,
|
|
4154
|
-
addOnOptionIds: item.add_on_option_ids || void 0,
|
|
524
|
+
addOnOptionIds: normalizeOptionIds(item.add_on_option_ids || void 0),
|
|
4155
525
|
addOnOptions: (item.add_on_options || []).map((option) => ({
|
|
4156
526
|
id: option.id,
|
|
4157
527
|
name: option.name,
|
|
@@ -4163,7 +533,7 @@ function mapServerCart(serverCart) {
|
|
|
4163
533
|
scheduledStart: item.scheduled_start || void 0,
|
|
4164
534
|
scheduledEnd: item.scheduled_end || void 0,
|
|
4165
535
|
staffId: item.staff_id || void 0
|
|
4166
|
-
};
|
|
536
|
+
});
|
|
4167
537
|
});
|
|
4168
538
|
return {
|
|
4169
539
|
items,
|
|
@@ -4193,6 +563,7 @@ function createCartStore(params) {
|
|
|
4193
563
|
let snapshot = createEmptySnapshot(currency);
|
|
4194
564
|
let initialized = false;
|
|
4195
565
|
let initializePromise = null;
|
|
566
|
+
let persistTimer = null;
|
|
4196
567
|
function emit() {
|
|
4197
568
|
listeners.forEach((listener) => {
|
|
4198
569
|
listener();
|
|
@@ -4200,7 +571,7 @@ function createCartStore(params) {
|
|
|
4200
571
|
}
|
|
4201
572
|
function setSnapshot(nextSnapshot) {
|
|
4202
573
|
snapshot = nextSnapshot;
|
|
4203
|
-
|
|
574
|
+
schedulePersist();
|
|
4204
575
|
emit();
|
|
4205
576
|
}
|
|
4206
577
|
function updateSnapshot(updater) {
|
|
@@ -4214,7 +585,7 @@ function createCartStore(params) {
|
|
|
4214
585
|
window.localStorage.setItem(
|
|
4215
586
|
storageKey,
|
|
4216
587
|
JSON.stringify({
|
|
4217
|
-
items: snapshot.items,
|
|
588
|
+
items: snapshot.items.map((item) => stripInternalItemMetadata(item)),
|
|
4218
589
|
subtotal: snapshot.subtotal,
|
|
4219
590
|
tax: snapshot.tax,
|
|
4220
591
|
total: snapshot.total,
|
|
@@ -4224,6 +595,15 @@ function createCartStore(params) {
|
|
|
4224
595
|
} catch {
|
|
4225
596
|
}
|
|
4226
597
|
}
|
|
598
|
+
function schedulePersist() {
|
|
599
|
+
if (persistTimer) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
persistTimer = setTimeout(() => {
|
|
603
|
+
persistTimer = null;
|
|
604
|
+
persistSnapshot();
|
|
605
|
+
}, 100);
|
|
606
|
+
}
|
|
4227
607
|
function hydrateFromStorage() {
|
|
4228
608
|
if (typeof window === "undefined" || !window.localStorage) {
|
|
4229
609
|
return;
|
|
@@ -4238,7 +618,7 @@ function createCartStore(params) {
|
|
|
4238
618
|
return;
|
|
4239
619
|
}
|
|
4240
620
|
snapshot = {
|
|
4241
|
-
items: parsed.items,
|
|
621
|
+
items: parsed.items.map((item) => attachInternalItemMetadata(item)),
|
|
4242
622
|
subtotal: toNumber(parsed.subtotal),
|
|
4243
623
|
tax: toNumber(parsed.tax),
|
|
4244
624
|
total: toNumber(parsed.total),
|
|
@@ -4315,7 +695,7 @@ function createCartStore(params) {
|
|
|
4315
695
|
}
|
|
4316
696
|
const previousSnapshot = snapshot;
|
|
4317
697
|
const lineKey = buildLineKey(product.id, normalizedOptions);
|
|
4318
|
-
const optimisticItem = {
|
|
698
|
+
const optimisticItem = attachInternalItemMetadata({
|
|
4319
699
|
id: `tmp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
|
|
4320
700
|
product,
|
|
4321
701
|
quantity: resolvedQuantity,
|
|
@@ -4331,21 +711,11 @@ function createCartStore(params) {
|
|
|
4331
711
|
scheduledStart: normalizedOptions.scheduledStart,
|
|
4332
712
|
scheduledEnd: normalizedOptions.scheduledEnd,
|
|
4333
713
|
staffId: normalizedOptions.staffId
|
|
4334
|
-
};
|
|
714
|
+
});
|
|
4335
715
|
updateSnapshot((current) => {
|
|
4336
|
-
const existingIndex = current.items.findIndex(
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
variantId: item.variantId,
|
|
4340
|
-
quoteId: item.quoteId,
|
|
4341
|
-
addOnOptionIds: item.addOnOptionIds,
|
|
4342
|
-
bundleSelections: item.bundleSelections,
|
|
4343
|
-
compositeSelections: item.compositeSelections,
|
|
4344
|
-
scheduledStart: item.scheduledStart,
|
|
4345
|
-
staffId: item.staffId
|
|
4346
|
-
});
|
|
4347
|
-
return itemKey === lineKey;
|
|
4348
|
-
});
|
|
716
|
+
const existingIndex = current.items.findIndex(
|
|
717
|
+
(item) => (item._lineKey ?? lineKeyFromItem(item)) === lineKey
|
|
718
|
+
);
|
|
4349
719
|
const nextItems = [...current.items];
|
|
4350
720
|
if (existingIndex >= 0) {
|
|
4351
721
|
const existing = nextItems[existingIndex];
|
|
@@ -4555,7 +925,7 @@ function getOrCreateStore(params) {
|
|
|
4555
925
|
return created;
|
|
4556
926
|
}
|
|
4557
927
|
function useCart(options = {}) {
|
|
4558
|
-
const context =
|
|
928
|
+
const context = useOptionalCimplifyClient();
|
|
4559
929
|
const client = options.client ?? context?.client;
|
|
4560
930
|
if (!client) {
|
|
4561
931
|
throw new Error("useCart must be used within CimplifyProvider or passed { client }.");
|
|
@@ -4693,7 +1063,8 @@ function resolveInitialLocation(locations) {
|
|
|
4693
1063
|
}
|
|
4694
1064
|
return locations[0];
|
|
4695
1065
|
}
|
|
4696
|
-
var
|
|
1066
|
+
var CimplifyClientContext = createContext(null);
|
|
1067
|
+
var CimplifyStateContext = createContext(null);
|
|
4697
1068
|
function CimplifyProvider({
|
|
4698
1069
|
client,
|
|
4699
1070
|
children,
|
|
@@ -4832,49 +1203,76 @@ function CimplifyProvider({
|
|
|
4832
1203
|
cancelled = true;
|
|
4833
1204
|
};
|
|
4834
1205
|
}, [resolvedClient, isDemoMode]);
|
|
4835
|
-
const
|
|
1206
|
+
const clientContextValue = useMemo(
|
|
4836
1207
|
() => ({
|
|
4837
1208
|
client: resolvedClient,
|
|
4838
1209
|
business,
|
|
4839
1210
|
currency: baseCurrency,
|
|
4840
1211
|
country: business?.country_code || DEFAULT_COUNTRY,
|
|
4841
1212
|
locations,
|
|
4842
|
-
currentLocation,
|
|
4843
|
-
setCurrentLocation,
|
|
4844
1213
|
isReady,
|
|
4845
1214
|
isDemoMode,
|
|
1215
|
+
baseCurrency
|
|
1216
|
+
}),
|
|
1217
|
+
[
|
|
1218
|
+
resolvedClient,
|
|
1219
|
+
business,
|
|
4846
1220
|
baseCurrency,
|
|
1221
|
+
locations,
|
|
1222
|
+
isReady,
|
|
1223
|
+
isDemoMode
|
|
1224
|
+
]
|
|
1225
|
+
);
|
|
1226
|
+
const stateContextValue = useMemo(
|
|
1227
|
+
() => ({
|
|
1228
|
+
currentLocation,
|
|
1229
|
+
setCurrentLocation,
|
|
4847
1230
|
displayCurrency,
|
|
4848
1231
|
setDisplayCurrency,
|
|
4849
1232
|
convertPrice,
|
|
4850
1233
|
fxRate
|
|
4851
1234
|
}),
|
|
4852
1235
|
[
|
|
4853
|
-
resolvedClient,
|
|
4854
|
-
business,
|
|
4855
|
-
baseCurrency,
|
|
4856
|
-
locations,
|
|
4857
1236
|
currentLocation,
|
|
4858
1237
|
setCurrentLocation,
|
|
4859
|
-
isReady,
|
|
4860
|
-
isDemoMode,
|
|
4861
1238
|
displayCurrency,
|
|
4862
1239
|
setDisplayCurrency,
|
|
4863
1240
|
convertPrice,
|
|
4864
1241
|
fxRate
|
|
4865
1242
|
]
|
|
4866
1243
|
);
|
|
4867
|
-
return /* @__PURE__ */ jsx(
|
|
1244
|
+
return /* @__PURE__ */ jsx(CimplifyClientContext.Provider, { value: clientContextValue, children: /* @__PURE__ */ jsx(CimplifyStateContext.Provider, { value: stateContextValue, children }) });
|
|
4868
1245
|
}
|
|
4869
1246
|
function useCimplify() {
|
|
4870
|
-
const
|
|
4871
|
-
|
|
1247
|
+
const clientContext = useContext(CimplifyClientContext);
|
|
1248
|
+
const stateContext = useContext(CimplifyStateContext);
|
|
1249
|
+
if (!clientContext || !stateContext) {
|
|
4872
1250
|
throw new Error("useCimplify must be used within CimplifyProvider");
|
|
4873
1251
|
}
|
|
4874
|
-
return
|
|
1252
|
+
return useMemo(
|
|
1253
|
+
() => ({ ...clientContext, ...stateContext }),
|
|
1254
|
+
[clientContext, stateContext]
|
|
1255
|
+
);
|
|
4875
1256
|
}
|
|
4876
1257
|
function useOptionalCimplify() {
|
|
4877
|
-
|
|
1258
|
+
const clientContext = useContext(CimplifyClientContext);
|
|
1259
|
+
const stateContext = useContext(CimplifyStateContext);
|
|
1260
|
+
return useMemo(() => {
|
|
1261
|
+
if (!clientContext || !stateContext) {
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
return { ...clientContext, ...stateContext };
|
|
1265
|
+
}, [clientContext, stateContext]);
|
|
1266
|
+
}
|
|
1267
|
+
function useCimplifyClient() {
|
|
1268
|
+
const context = useContext(CimplifyClientContext);
|
|
1269
|
+
if (!context) {
|
|
1270
|
+
throw new Error("useCimplifyClient must be used within CimplifyProvider");
|
|
1271
|
+
}
|
|
1272
|
+
return context;
|
|
1273
|
+
}
|
|
1274
|
+
function useOptionalCimplifyClient() {
|
|
1275
|
+
return useContext(CimplifyClientContext);
|
|
4878
1276
|
}
|
|
4879
1277
|
var SPACE = { sm: 8};
|
|
4880
1278
|
function shellColors(isDark, primaryColor) {
|
|
@@ -5259,14 +1657,26 @@ function Price({ amount, className, prefix }) {
|
|
|
5259
1657
|
var productsCache = /* @__PURE__ */ new Map();
|
|
5260
1658
|
var productsInflight = /* @__PURE__ */ new Map();
|
|
5261
1659
|
function buildProductsCacheKey(client, locationId, options) {
|
|
5262
|
-
return
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
options
|
|
5266
|
-
|
|
1660
|
+
return [
|
|
1661
|
+
client.getPublicKey(),
|
|
1662
|
+
locationId ?? "",
|
|
1663
|
+
options.category ?? "",
|
|
1664
|
+
options.collection ?? "",
|
|
1665
|
+
options.taxonomy ?? "",
|
|
1666
|
+
options.search ?? "",
|
|
1667
|
+
options.featured ?? "",
|
|
1668
|
+
options.limit ?? "",
|
|
1669
|
+
options.sort_by ?? "",
|
|
1670
|
+
options.sort_order ?? "",
|
|
1671
|
+
options.tags?.join(",") ?? "",
|
|
1672
|
+
options.in_stock ?? "",
|
|
1673
|
+
options.min_price ?? "",
|
|
1674
|
+
options.max_price ?? "",
|
|
1675
|
+
options.page ?? ""
|
|
1676
|
+
].join("|");
|
|
5267
1677
|
}
|
|
5268
1678
|
function useProducts(options = {}) {
|
|
5269
|
-
const context =
|
|
1679
|
+
const context = useOptionalCimplifyClient();
|
|
5270
1680
|
const client = options.client ?? context?.client;
|
|
5271
1681
|
if (!client) {
|
|
5272
1682
|
throw new Error("useProducts must be used within CimplifyProvider or passed { client }.");
|
|
@@ -5275,6 +1685,7 @@ function useProducts(options = {}) {
|
|
|
5275
1685
|
const locationId = client.getLocationId();
|
|
5276
1686
|
const previousLocationIdRef = useRef(locationId);
|
|
5277
1687
|
const requestIdRef = useRef(0);
|
|
1688
|
+
const tagsSignature = options.tags?.join(",") ?? "";
|
|
5278
1689
|
const queryOptions = useMemo(
|
|
5279
1690
|
() => ({
|
|
5280
1691
|
category: options.category,
|
|
@@ -5300,7 +1711,7 @@ function useProducts(options = {}) {
|
|
|
5300
1711
|
options.limit,
|
|
5301
1712
|
options.sort_by,
|
|
5302
1713
|
options.sort_order,
|
|
5303
|
-
|
|
1714
|
+
tagsSignature,
|
|
5304
1715
|
options.in_stock,
|
|
5305
1716
|
options.min_price,
|
|
5306
1717
|
options.max_price,
|
|
@@ -5418,7 +1829,7 @@ function buildProductCacheKey(client, locationId, slugOrId) {
|
|
|
5418
1829
|
});
|
|
5419
1830
|
}
|
|
5420
1831
|
function useProduct(slugOrId, options = {}) {
|
|
5421
|
-
const context =
|
|
1832
|
+
const context = useOptionalCimplifyClient();
|
|
5422
1833
|
const client = options.client ?? context?.client;
|
|
5423
1834
|
if (!client) {
|
|
5424
1835
|
throw new Error("useProduct must be used within CimplifyProvider or passed { client }.");
|
|
@@ -5522,7 +1933,7 @@ function buildCategoriesCacheKey(client) {
|
|
|
5522
1933
|
return client.getPublicKey() || "__demo__";
|
|
5523
1934
|
}
|
|
5524
1935
|
function useCategories(options = {}) {
|
|
5525
|
-
const context =
|
|
1936
|
+
const context = useOptionalCimplifyClient();
|
|
5526
1937
|
const client = options.client ?? context?.client;
|
|
5527
1938
|
if (!client) {
|
|
5528
1939
|
throw new Error("useCategories must be used within CimplifyProvider or passed { client }.");
|
|
@@ -5592,7 +2003,7 @@ function buildOrderCacheKey(client, orderId) {
|
|
|
5592
2003
|
return `${client.getPublicKey() || "__demo__"}:${orderId}`;
|
|
5593
2004
|
}
|
|
5594
2005
|
function useOrder(orderId, options = {}) {
|
|
5595
|
-
const context =
|
|
2006
|
+
const context = useOptionalCimplifyClient();
|
|
5596
2007
|
const client = options.client ?? context?.client;
|
|
5597
2008
|
if (!client) {
|
|
5598
2009
|
throw new Error("useOrder must be used within CimplifyProvider or passed { client }.");
|
|
@@ -5794,7 +2205,7 @@ function buildCollectionsCacheKey(client, locationId) {
|
|
|
5794
2205
|
});
|
|
5795
2206
|
}
|
|
5796
2207
|
function useCollections(options = {}) {
|
|
5797
|
-
const context =
|
|
2208
|
+
const context = useOptionalCimplifyClient();
|
|
5798
2209
|
const client = options.client ?? context?.client;
|
|
5799
2210
|
if (!client) {
|
|
5800
2211
|
throw new Error("useCollections must be used within CimplifyProvider or passed { client }.");
|
|
@@ -5890,7 +2301,7 @@ function buildCollectionCacheKey(client, locationId, idOrSlug) {
|
|
|
5890
2301
|
});
|
|
5891
2302
|
}
|
|
5892
2303
|
function useCollection(idOrSlug, options = {}) {
|
|
5893
|
-
const context =
|
|
2304
|
+
const context = useOptionalCimplifyClient();
|
|
5894
2305
|
const client = options.client ?? context?.client;
|
|
5895
2306
|
if (!client) {
|
|
5896
2307
|
throw new Error("useCollection must be used within CimplifyProvider or passed { client }.");
|
|
@@ -5991,7 +2402,7 @@ function useCollection(idOrSlug, options = {}) {
|
|
|
5991
2402
|
return { collection, products, isLoading, error, refetch };
|
|
5992
2403
|
}
|
|
5993
2404
|
function useSearch(options = {}) {
|
|
5994
|
-
const context =
|
|
2405
|
+
const context = useOptionalCimplifyClient();
|
|
5995
2406
|
const client = options.client ?? context?.client;
|
|
5996
2407
|
if (!client) {
|
|
5997
2408
|
throw new Error("useSearch must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6071,11 +2482,46 @@ function useSearch(options = {}) {
|
|
|
6071
2482
|
var quoteCache = /* @__PURE__ */ new Map();
|
|
6072
2483
|
var quoteInflight = /* @__PURE__ */ new Map();
|
|
6073
2484
|
function buildQuoteCacheKey(client, locationId, inputSignature) {
|
|
6074
|
-
return
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
2485
|
+
return [client.getPublicKey(), locationId ?? "", inputSignature].join("|");
|
|
2486
|
+
}
|
|
2487
|
+
function serializeQuoteBundleSelections(selections) {
|
|
2488
|
+
if (!selections || selections.length === 0) {
|
|
2489
|
+
return "";
|
|
2490
|
+
}
|
|
2491
|
+
return selections.map((selection) => {
|
|
2492
|
+
return [
|
|
2493
|
+
selection.component_id,
|
|
2494
|
+
selection.quantity,
|
|
2495
|
+
selection.variant_id ?? ""
|
|
2496
|
+
].join(":");
|
|
2497
|
+
}).join(";");
|
|
2498
|
+
}
|
|
2499
|
+
function serializeQuoteCompositeSelections(selections) {
|
|
2500
|
+
if (!selections || selections.length === 0) {
|
|
2501
|
+
return "";
|
|
2502
|
+
}
|
|
2503
|
+
return selections.map((selection) => {
|
|
2504
|
+
return [
|
|
2505
|
+
selection.component_id,
|
|
2506
|
+
selection.quantity,
|
|
2507
|
+
selection.variant_id ?? "",
|
|
2508
|
+
selection.add_on_option_id ?? ""
|
|
2509
|
+
].join(":");
|
|
2510
|
+
}).join(";");
|
|
2511
|
+
}
|
|
2512
|
+
function buildQuoteInputSignature(input) {
|
|
2513
|
+
if (!input) {
|
|
2514
|
+
return "";
|
|
2515
|
+
}
|
|
2516
|
+
return [
|
|
2517
|
+
input.productId,
|
|
2518
|
+
input.variantId ?? "",
|
|
2519
|
+
input.locationId ?? "",
|
|
2520
|
+
input.quantity ?? "",
|
|
2521
|
+
input.addOnOptionIds?.join(",") ?? "",
|
|
2522
|
+
serializeQuoteBundleSelections(input.bundleSelections),
|
|
2523
|
+
serializeQuoteCompositeSelections(input.compositeSelections)
|
|
2524
|
+
].join("|");
|
|
6079
2525
|
}
|
|
6080
2526
|
function isQuoteExpired(quote) {
|
|
6081
2527
|
if (!quote?.expires_at) {
|
|
@@ -6102,7 +2548,7 @@ function normalizeInput(input, fallbackLocationId) {
|
|
|
6102
2548
|
};
|
|
6103
2549
|
}
|
|
6104
2550
|
function useQuote(input, options = {}) {
|
|
6105
|
-
const context =
|
|
2551
|
+
const context = useOptionalCimplifyClient();
|
|
6106
2552
|
const client = options.client ?? context?.client;
|
|
6107
2553
|
if (!client) {
|
|
6108
2554
|
throw new Error("useQuote must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6112,10 +2558,8 @@ function useQuote(input, options = {}) {
|
|
|
6112
2558
|
const refreshBeforeExpiryMs = Math.max(0, options.refreshBeforeExpiryMs ?? 3e4);
|
|
6113
2559
|
const locationId = client.getLocationId();
|
|
6114
2560
|
const requestIdRef = useRef(0);
|
|
6115
|
-
const refreshTimerRef = useRef(null);
|
|
6116
|
-
const expiryTimerRef = useRef(null);
|
|
6117
2561
|
const debounceTimerRef = useRef(null);
|
|
6118
|
-
const inputSignature =
|
|
2562
|
+
const inputSignature = buildQuoteInputSignature(input);
|
|
6119
2563
|
const normalizedInput = useMemo(() => {
|
|
6120
2564
|
if (!input) {
|
|
6121
2565
|
return null;
|
|
@@ -6133,6 +2577,13 @@ function useQuote(input, options = {}) {
|
|
|
6133
2577
|
const [error, setError] = useState(null);
|
|
6134
2578
|
const [isExpired, setIsExpired] = useState(isQuoteExpired(cached?.quote ?? null));
|
|
6135
2579
|
const [messages, setMessages] = useState(cached?.quote?.ui_messages ?? []);
|
|
2580
|
+
const quoteExpiryTimestamp = useMemo(() => {
|
|
2581
|
+
if (!quote?.expires_at) {
|
|
2582
|
+
return Number.NaN;
|
|
2583
|
+
}
|
|
2584
|
+
const expiresAt = Date.parse(quote.expires_at);
|
|
2585
|
+
return Number.isFinite(expiresAt) ? expiresAt : Number.NaN;
|
|
2586
|
+
}, [quote?.expires_at]);
|
|
6136
2587
|
const load = useCallback(
|
|
6137
2588
|
async (force = false) => {
|
|
6138
2589
|
if (!enabled || !normalizedInput) {
|
|
@@ -6250,52 +2701,34 @@ function useQuote(input, options = {}) {
|
|
|
6250
2701
|
}
|
|
6251
2702
|
}, [cacheKey, client, enabled, load, normalizedInput, quote]);
|
|
6252
2703
|
useEffect(() => {
|
|
6253
|
-
if (
|
|
6254
|
-
clearTimeout(expiryTimerRef.current);
|
|
6255
|
-
expiryTimerRef.current = null;
|
|
6256
|
-
}
|
|
6257
|
-
const expiresAt = quote?.expires_at ? Date.parse(quote.expires_at) : NaN;
|
|
6258
|
-
if (!Number.isFinite(expiresAt)) {
|
|
2704
|
+
if (!Number.isFinite(quoteExpiryTimestamp)) {
|
|
6259
2705
|
setIsExpired(false);
|
|
6260
2706
|
return;
|
|
6261
2707
|
}
|
|
6262
|
-
const
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
expiryTimerRef.current = null;
|
|
6273
|
-
}
|
|
6274
|
-
};
|
|
6275
|
-
}, [quote?.expires_at, quote?.quote_id]);
|
|
6276
|
-
useEffect(() => {
|
|
6277
|
-
if (refreshTimerRef.current) {
|
|
6278
|
-
clearTimeout(refreshTimerRef.current);
|
|
6279
|
-
refreshTimerRef.current = null;
|
|
6280
|
-
}
|
|
6281
|
-
if (!autoRefresh || !enabled || !quote?.expires_at) {
|
|
6282
|
-
return;
|
|
2708
|
+
const now = Date.now();
|
|
2709
|
+
const isCurrentlyExpired = quoteExpiryTimestamp <= now;
|
|
2710
|
+
const timers = [];
|
|
2711
|
+
setIsExpired(isCurrentlyExpired);
|
|
2712
|
+
if (!isCurrentlyExpired) {
|
|
2713
|
+
timers.push(
|
|
2714
|
+
setTimeout(() => {
|
|
2715
|
+
setIsExpired(true);
|
|
2716
|
+
}, Math.max(0, quoteExpiryTimestamp - now))
|
|
2717
|
+
);
|
|
6283
2718
|
}
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
2719
|
+
if (autoRefresh && enabled && !isCurrentlyExpired) {
|
|
2720
|
+
timers.push(
|
|
2721
|
+
setTimeout(() => {
|
|
2722
|
+
void refresh();
|
|
2723
|
+
}, Math.max(0, quoteExpiryTimestamp - now - refreshBeforeExpiryMs))
|
|
2724
|
+
);
|
|
6287
2725
|
}
|
|
6288
|
-
const delay = Math.max(0, expiresAt - Date.now() - refreshBeforeExpiryMs);
|
|
6289
|
-
refreshTimerRef.current = setTimeout(() => {
|
|
6290
|
-
void refresh();
|
|
6291
|
-
}, delay);
|
|
6292
2726
|
return () => {
|
|
6293
|
-
|
|
6294
|
-
clearTimeout(
|
|
6295
|
-
refreshTimerRef.current = null;
|
|
2727
|
+
for (const timer of timers) {
|
|
2728
|
+
clearTimeout(timer);
|
|
6296
2729
|
}
|
|
6297
2730
|
};
|
|
6298
|
-
}, [autoRefresh, enabled,
|
|
2731
|
+
}, [autoRefresh, enabled, quoteExpiryTimestamp, refresh, refreshBeforeExpiryMs]);
|
|
6299
2732
|
return { quote, isLoading, error, refresh, isExpired, messages };
|
|
6300
2733
|
}
|
|
6301
2734
|
var taxonomiesCache = /* @__PURE__ */ new Map();
|
|
@@ -6307,7 +2740,7 @@ function buildTaxonomiesCacheKey(client, parentId) {
|
|
|
6307
2740
|
});
|
|
6308
2741
|
}
|
|
6309
2742
|
function useTaxonomies(options = {}) {
|
|
6310
|
-
const context =
|
|
2743
|
+
const context = useOptionalCimplifyClient();
|
|
6311
2744
|
const client = options.client ?? context?.client;
|
|
6312
2745
|
if (!client) {
|
|
6313
2746
|
throw new Error("useTaxonomies must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6384,7 +2817,7 @@ function buildTaxonomyCacheKey(client, id) {
|
|
|
6384
2817
|
});
|
|
6385
2818
|
}
|
|
6386
2819
|
function useTaxonomy(id, options = {}) {
|
|
6387
|
-
const context =
|
|
2820
|
+
const context = useOptionalCimplifyClient();
|
|
6388
2821
|
const client = options.client ?? context?.client;
|
|
6389
2822
|
if (!client) {
|
|
6390
2823
|
throw new Error("useTaxonomy must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6464,7 +2897,7 @@ function buildTaxonomyPathCacheKey(client, id) {
|
|
|
6464
2897
|
});
|
|
6465
2898
|
}
|
|
6466
2899
|
function useTaxonomyPath(id, options = {}) {
|
|
6467
|
-
const context =
|
|
2900
|
+
const context = useOptionalCimplifyClient();
|
|
6468
2901
|
const client = options.client ?? context?.client;
|
|
6469
2902
|
if (!client) {
|
|
6470
2903
|
throw new Error("useTaxonomyPath must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6537,7 +2970,7 @@ function buildSchedulesCacheKey(client, productId) {
|
|
|
6537
2970
|
});
|
|
6538
2971
|
}
|
|
6539
2972
|
function useProductSchedules(productId, options = {}) {
|
|
6540
|
-
const context =
|
|
2973
|
+
const context = useOptionalCimplifyClient();
|
|
6541
2974
|
const client = options.client ?? context?.client;
|
|
6542
2975
|
if (!client) {
|
|
6543
2976
|
throw new Error("useProductSchedules must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6611,7 +3044,7 @@ function buildAvailabilityCacheKey(client, productId, locationId) {
|
|
|
6611
3044
|
});
|
|
6612
3045
|
}
|
|
6613
3046
|
function useProductAvailability(productId, options = {}) {
|
|
6614
|
-
const context =
|
|
3047
|
+
const context = useOptionalCimplifyClient();
|
|
6615
3048
|
const client = options.client ?? context?.client;
|
|
6616
3049
|
if (!client) {
|
|
6617
3050
|
throw new Error("useProductAvailability must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6700,7 +3133,7 @@ function buildDealsCacheKey(client, locationId) {
|
|
|
6700
3133
|
return JSON.stringify({ key: client.getPublicKey(), location_id: locationId });
|
|
6701
3134
|
}
|
|
6702
3135
|
function useDeals(options = {}) {
|
|
6703
|
-
const context =
|
|
3136
|
+
const context = useOptionalCimplifyClient();
|
|
6704
3137
|
const client = options.client ?? context?.client;
|
|
6705
3138
|
if (!client) {
|
|
6706
3139
|
throw new Error("useDeals must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6779,7 +3212,7 @@ function buildOnSaleCacheKey(client) {
|
|
|
6779
3212
|
return client.getPublicKey() || "__demo__";
|
|
6780
3213
|
}
|
|
6781
3214
|
function useProductsOnSale(options = {}) {
|
|
6782
|
-
const context =
|
|
3215
|
+
const context = useOptionalCimplifyClient();
|
|
6783
3216
|
const client = options.client ?? context?.client;
|
|
6784
3217
|
if (!client) {
|
|
6785
3218
|
throw new Error("useProductsOnSale must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6849,7 +3282,7 @@ function buildProductDealsCacheKey(client, productId) {
|
|
|
6849
3282
|
return JSON.stringify({ key: client.getPublicKey(), product_id: productId });
|
|
6850
3283
|
}
|
|
6851
3284
|
function useProductDeals(productId, options = {}) {
|
|
6852
|
-
const context =
|
|
3285
|
+
const context = useOptionalCimplifyClient();
|
|
6853
3286
|
const client = options.client ?? context?.client;
|
|
6854
3287
|
if (!client) {
|
|
6855
3288
|
throw new Error("useProductDeals must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6917,7 +3350,7 @@ function useProductDeals(productId, options = {}) {
|
|
|
6917
3350
|
return { deals, isLoading, error, refetch };
|
|
6918
3351
|
}
|
|
6919
3352
|
function useValidateDiscount(options = {}) {
|
|
6920
|
-
const context =
|
|
3353
|
+
const context = useOptionalCimplifyClient();
|
|
6921
3354
|
const client = options.client ?? context?.client;
|
|
6922
3355
|
if (!client) {
|
|
6923
3356
|
throw new Error("useValidateDiscount must be used within CimplifyProvider or passed { client }.");
|
|
@@ -6955,7 +3388,7 @@ function buildBundlesCacheKey(client, locationId) {
|
|
|
6955
3388
|
});
|
|
6956
3389
|
}
|
|
6957
3390
|
function useBundles(options = {}) {
|
|
6958
|
-
const context =
|
|
3391
|
+
const context = useOptionalCimplifyClient();
|
|
6959
3392
|
const client = options.client ?? context?.client;
|
|
6960
3393
|
if (!client) {
|
|
6961
3394
|
throw new Error("useBundles must be used within CimplifyProvider or passed { client }.");
|
|
@@ -7081,7 +3514,7 @@ function buildOrdersCacheKey(client, status, limit) {
|
|
|
7081
3514
|
return JSON.stringify({ key: client.getPublicKey(), status, limit });
|
|
7082
3515
|
}
|
|
7083
3516
|
function useOrders(options = {}) {
|
|
7084
|
-
const context =
|
|
3517
|
+
const context = useOptionalCimplifyClient();
|
|
7085
3518
|
const client = options.client ?? context?.client;
|
|
7086
3519
|
if (!client) {
|
|
7087
3520
|
throw new Error("useOrders must be used within CimplifyProvider or passed { client }.");
|
|
@@ -7166,7 +3599,7 @@ function buildServicesCacheKey(client) {
|
|
|
7166
3599
|
return JSON.stringify({ key: client.getPublicKey() });
|
|
7167
3600
|
}
|
|
7168
3601
|
function useServices(options = {}) {
|
|
7169
|
-
const context =
|
|
3602
|
+
const context = useOptionalCimplifyClient();
|
|
7170
3603
|
const client = options.client ?? context?.client;
|
|
7171
3604
|
if (!client) {
|
|
7172
3605
|
throw new Error("useServices must be used within CimplifyProvider or passed { client }.");
|
|
@@ -7250,7 +3683,7 @@ function buildSlotsCacheKey(client, serviceId, date, participantCount) {
|
|
|
7250
3683
|
});
|
|
7251
3684
|
}
|
|
7252
3685
|
function useAvailableSlots(serviceId, date, options = {}) {
|
|
7253
|
-
const context =
|
|
3686
|
+
const context = useOptionalCimplifyClient();
|
|
7254
3687
|
const client = options.client ?? context?.client;
|
|
7255
3688
|
if (!client) {
|
|
7256
3689
|
throw new Error("useAvailableSlots must be used within CimplifyProvider or passed { client }.");
|
|
@@ -7344,7 +3777,7 @@ function buildAvailabilityCacheKey2(client, serviceId, startDate, endDate, parti
|
|
|
7344
3777
|
});
|
|
7345
3778
|
}
|
|
7346
3779
|
function useServiceAvailability(serviceId, startDate, endDate, options = {}) {
|
|
7347
|
-
const context =
|
|
3780
|
+
const context = useOptionalCimplifyClient();
|
|
7348
3781
|
const client = options.client ?? context?.client;
|
|
7349
3782
|
if (!client) {
|
|
7350
3783
|
throw new Error(
|
|
@@ -7442,7 +3875,7 @@ function buildBookingsCacheKey(client, filter) {
|
|
|
7442
3875
|
return JSON.stringify({ key: client.getPublicKey(), filter });
|
|
7443
3876
|
}
|
|
7444
3877
|
function useBookings(options = {}) {
|
|
7445
|
-
const context =
|
|
3878
|
+
const context = useOptionalCimplifyClient();
|
|
7446
3879
|
const client = options.client ?? context?.client;
|
|
7447
3880
|
if (!client) {
|
|
7448
3881
|
throw new Error("useBookings must be used within CimplifyProvider or passed { client }.");
|
|
@@ -7821,43 +4254,6 @@ function QuantitySelector({
|
|
|
7821
4254
|
}
|
|
7822
4255
|
);
|
|
7823
4256
|
}
|
|
7824
|
-
|
|
7825
|
-
// src/utils/variant.ts
|
|
7826
|
-
function stripProductPrefix(variantName, productName) {
|
|
7827
|
-
if (!variantName || !productName) return variantName || productName;
|
|
7828
|
-
const lower = variantName.toLowerCase().trim();
|
|
7829
|
-
if (lower === "default" || lower === "standard") return productName;
|
|
7830
|
-
if (lower.endsWith("-default") || lower.endsWith("-standard")) return productName;
|
|
7831
|
-
if (variantName.startsWith(productName)) {
|
|
7832
|
-
const rest = variantName.slice(productName.length);
|
|
7833
|
-
const match = rest.match(/^\s*[-–—]\s*(.*)/);
|
|
7834
|
-
if (match && match[1].length > 0) return match[1];
|
|
7835
|
-
}
|
|
7836
|
-
const lowerProduct = productName.toLowerCase().trim();
|
|
7837
|
-
if (lower.startsWith(lowerProduct)) {
|
|
7838
|
-
const rest = variantName.slice(productName.length);
|
|
7839
|
-
const match = rest.match(/^\s*[-–—]\s*(.*)/);
|
|
7840
|
-
if (match && match[1].length > 0) return match[1];
|
|
7841
|
-
}
|
|
7842
|
-
const lastDash = variantName.lastIndexOf("-");
|
|
7843
|
-
if (lastDash > 0 && lastDash < variantName.length - 1) {
|
|
7844
|
-
const prefix = variantName.slice(0, lastDash).trim();
|
|
7845
|
-
const suffix = variantName.slice(lastDash + 1).trim();
|
|
7846
|
-
if (suffix.length > 0 && prefix.length > suffix.length * 2) {
|
|
7847
|
-
return suffix;
|
|
7848
|
-
}
|
|
7849
|
-
}
|
|
7850
|
-
return variantName;
|
|
7851
|
-
}
|
|
7852
|
-
function getVariantDisplayName(variant, productName) {
|
|
7853
|
-
if (variant.display_attributes?.length) {
|
|
7854
|
-
return variant.display_attributes.map((a) => a.value_name).join(" / ");
|
|
7855
|
-
}
|
|
7856
|
-
if (variant.name) {
|
|
7857
|
-
return productName ? stripProductPrefix(variant.name, productName) : variant.name;
|
|
7858
|
-
}
|
|
7859
|
-
return productName || (variant.is_default ? "Default" : "Option");
|
|
7860
|
-
}
|
|
7861
4257
|
function VariantSelector({
|
|
7862
4258
|
variants,
|
|
7863
4259
|
variantAxes,
|
|
@@ -8032,13 +4428,14 @@ function AddOnSelector({
|
|
|
8032
4428
|
className,
|
|
8033
4429
|
classNames
|
|
8034
4430
|
}) {
|
|
4431
|
+
const selectedSet = useMemo(() => new Set(selectedOptions), [selectedOptions]);
|
|
8035
4432
|
const isOptionSelected = useCallback(
|
|
8036
|
-
(optionId) =>
|
|
8037
|
-
[
|
|
4433
|
+
(optionId) => selectedSet.has(optionId),
|
|
4434
|
+
[selectedSet]
|
|
8038
4435
|
);
|
|
8039
4436
|
const handleCheckedChange = useCallback(
|
|
8040
4437
|
(addOn, optionId, checked) => {
|
|
8041
|
-
const isSelected =
|
|
4438
|
+
const isSelected = selectedSet.has(optionId);
|
|
8042
4439
|
if (addOn.is_mutually_exclusive || !addOn.is_multiple_allowed) {
|
|
8043
4440
|
const groupOptionIds = new Set(addOn.options.map((o) => o.id));
|
|
8044
4441
|
const withoutGroup = selectedOptions.filter((id) => !groupOptionIds.has(id));
|
|
@@ -8053,9 +4450,12 @@ function AddOnSelector({
|
|
|
8053
4450
|
if (isSelected) {
|
|
8054
4451
|
onOptionsChange(selectedOptions.filter((id) => id !== optionId));
|
|
8055
4452
|
} else {
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
4453
|
+
let currentCount = 0;
|
|
4454
|
+
for (const option of addOn.options) {
|
|
4455
|
+
if (selectedSet.has(option.id)) {
|
|
4456
|
+
currentCount += 1;
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
8059
4459
|
if (addOn.max_selections && currentCount >= addOn.max_selections) {
|
|
8060
4460
|
return;
|
|
8061
4461
|
}
|
|
@@ -8063,15 +4463,18 @@ function AddOnSelector({
|
|
|
8063
4463
|
}
|
|
8064
4464
|
}
|
|
8065
4465
|
},
|
|
8066
|
-
[selectedOptions, onOptionsChange]
|
|
4466
|
+
[selectedOptions, selectedSet, onOptionsChange]
|
|
8067
4467
|
);
|
|
8068
4468
|
if (!addOns || addOns.length === 0) {
|
|
8069
4469
|
return null;
|
|
8070
4470
|
}
|
|
8071
4471
|
return /* @__PURE__ */ jsx("div", { "data-cimplify-addon-selector": true, className: cn("space-y-6", className, classNames?.root), children: addOns.map((addOn) => {
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
4472
|
+
let currentSelections = 0;
|
|
4473
|
+
for (const option of addOn.options) {
|
|
4474
|
+
if (selectedSet.has(option.id)) {
|
|
4475
|
+
currentSelections += 1;
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
8075
4478
|
const minMet = !addOn.min_selections || currentSelections >= addOn.min_selections;
|
|
8076
4479
|
const isSingleSelect = addOn.is_mutually_exclusive || !addOn.is_multiple_allowed;
|
|
8077
4480
|
return /* @__PURE__ */ jsxs(
|
|
@@ -8438,7 +4841,7 @@ function CompositeSelector({
|
|
|
8438
4841
|
className,
|
|
8439
4842
|
classNames
|
|
8440
4843
|
}) {
|
|
8441
|
-
const { client } =
|
|
4844
|
+
const { client } = useCimplifyClient();
|
|
8442
4845
|
const [groupSelections, setGroupSelections] = useState({});
|
|
8443
4846
|
const [priceResult, setPriceResult] = useState(null);
|
|
8444
4847
|
const [isPriceLoading, setIsPriceLoading] = useState(false);
|
|
@@ -8468,6 +4871,13 @@ function CompositeSelector({
|
|
|
8468
4871
|
}
|
|
8469
4872
|
return true;
|
|
8470
4873
|
}, [groups, groupSelections]);
|
|
4874
|
+
const sortedGroups = useMemo(
|
|
4875
|
+
() => [...groups].sort((a, b) => a.display_order - b.display_order).map((group) => ({
|
|
4876
|
+
...group,
|
|
4877
|
+
_sortedComponents: group.components.filter((component) => component.is_available && !component.is_archived).sort((a, b) => a.display_order - b.display_order)
|
|
4878
|
+
})),
|
|
4879
|
+
[groups]
|
|
4880
|
+
);
|
|
8471
4881
|
useEffect(() => {
|
|
8472
4882
|
onReady?.(allGroupsSatisfied);
|
|
8473
4883
|
}, [allGroupsSatisfied, onReady]);
|
|
@@ -8558,7 +4968,7 @@ function CompositeSelector({
|
|
|
8558
4968
|
return null;
|
|
8559
4969
|
}
|
|
8560
4970
|
return /* @__PURE__ */ jsxs("div", { "data-cimplify-composite-selector": true, className: cn("space-y-6", className, classNames?.root), children: [
|
|
8561
|
-
|
|
4971
|
+
sortedGroups.map((group) => {
|
|
8562
4972
|
const groupSels = groupSelections[group.id] || {};
|
|
8563
4973
|
const totalSelected = Object.values(groupSels).reduce((sum, q) => sum + q, 0);
|
|
8564
4974
|
const minMet = totalSelected >= group.min_selections;
|
|
@@ -8623,7 +5033,7 @@ function CompositeSelector({
|
|
|
8623
5033
|
role: isSingleSelect ? "radiogroup" : "group",
|
|
8624
5034
|
"aria-label": group.name,
|
|
8625
5035
|
className: cn("divide-y divide-border", classNames?.components),
|
|
8626
|
-
children: group.
|
|
5036
|
+
children: group._sortedComponents.map((component) => {
|
|
8627
5037
|
const qty = groupSels[component.id] || 0;
|
|
8628
5038
|
const isSelected = qty > 0;
|
|
8629
5039
|
const displayName = component.display_name || component.id;
|
|
@@ -9707,6 +6117,15 @@ function ProductGrid({
|
|
|
9707
6117
|
const md = columns?.md ?? 2;
|
|
9708
6118
|
const lg = columns?.lg ?? 3;
|
|
9709
6119
|
const xl = columns?.xl ?? 4;
|
|
6120
|
+
const css = React3.useMemo(
|
|
6121
|
+
() => [
|
|
6122
|
+
`#${gridId}{display:grid;grid-template-columns:repeat(${sm},1fr);gap:1rem}`,
|
|
6123
|
+
`@media(min-width:768px){#${gridId}{grid-template-columns:repeat(${md},1fr)}}`,
|
|
6124
|
+
`@media(min-width:1024px){#${gridId}{grid-template-columns:repeat(${lg},1fr)}}`,
|
|
6125
|
+
`@media(min-width:1280px){#${gridId}{grid-template-columns:repeat(${xl},1fr)}}`
|
|
6126
|
+
].join(""),
|
|
6127
|
+
[gridId, sm, md, lg, xl]
|
|
6128
|
+
);
|
|
9710
6129
|
if (products.length === 0) {
|
|
9711
6130
|
return /* @__PURE__ */ jsx(
|
|
9712
6131
|
"div",
|
|
@@ -9718,12 +6137,6 @@ function ProductGrid({
|
|
|
9718
6137
|
}
|
|
9719
6138
|
);
|
|
9720
6139
|
}
|
|
9721
|
-
const css = [
|
|
9722
|
-
`#${gridId}{display:grid;grid-template-columns:repeat(${sm},1fr);gap:1rem}`,
|
|
9723
|
-
`@media(min-width:768px){#${gridId}{grid-template-columns:repeat(${md},1fr)}}`,
|
|
9724
|
-
`@media(min-width:1024px){#${gridId}{grid-template-columns:repeat(${lg},1fr)}}`,
|
|
9725
|
-
`@media(min-width:1280px){#${gridId}{grid-template-columns:repeat(${xl},1fr)}}`
|
|
9726
|
-
].join("");
|
|
9727
6140
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9728
6141
|
/* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: css } }),
|
|
9729
6142
|
/* @__PURE__ */ jsx(
|
|
@@ -11328,7 +7741,7 @@ function CheckoutPage({
|
|
|
11328
7741
|
className,
|
|
11329
7742
|
classNames
|
|
11330
7743
|
}) {
|
|
11331
|
-
const { client } =
|
|
7744
|
+
const { client } = useCimplifyClient();
|
|
11332
7745
|
return /* @__PURE__ */ jsxs("div", { "data-cimplify-checkout-page": true, className: cn(className, classNames?.root), children: [
|
|
11333
7746
|
/* @__PURE__ */ jsx("div", { "data-cimplify-checkout-header": true, className: classNames?.header, children: /* @__PURE__ */ jsx("h1", { "data-cimplify-checkout-title": true, className: classNames?.title, children: title }) }),
|
|
11334
7747
|
/* @__PURE__ */ jsx(
|
|
@@ -11702,8 +8115,8 @@ function BookingPage({
|
|
|
11702
8115
|
staffId: selectedStaffId || void 0
|
|
11703
8116
|
});
|
|
11704
8117
|
onBooked?.(selectedSlot, selectedStaffId);
|
|
11705
|
-
} catch (
|
|
11706
|
-
setError(
|
|
8118
|
+
} catch (err) {
|
|
8119
|
+
setError(err instanceof Error ? err.message : "Failed to add booking to cart");
|
|
11707
8120
|
} finally {
|
|
11708
8121
|
setIsSubmitting(false);
|
|
11709
8122
|
}
|
|
@@ -11900,4 +8313,4 @@ function BookingsPage({
|
|
|
11900
8313
|
] });
|
|
11901
8314
|
}
|
|
11902
8315
|
|
|
11903
|
-
export { Ad, AdProvider, AddOnSelector, AddressElement, AuthElement, AvailabilityBadge, BookingCard, BookingList, BookingPage, BookingsPage, BundleSelector, CartPage, CartSummary, CataloguePage, CategoryFilter, CategoryGrid, CheckoutPage, CimplifyCheckout, CimplifyProvider, CollectionPage, CompositeSelector, DateSlotPicker, DealBanner, DealsPage, DiscountInput, ElementsProvider, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderSummary, PaymentElement, Price, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductPage, ProductSheet, QuantitySelector, SaleBadge, SearchInput, SearchPage, ServiceCard, ServiceGrid, SlotPicker, StaffPicker, StoreNav, VariantSelector, cn,
|
|
8316
|
+
export { Ad, AdProvider, AddOnSelector, AddressElement, AuthElement, AvailabilityBadge, BookingCard, BookingList, BookingPage, BookingsPage, BundleSelector, CartPage, CartSummary, CataloguePage, CategoryFilter, CategoryGrid, CheckoutPage, CimplifyCheckout, CimplifyProvider, CollectionPage, CompositeSelector, DateSlotPicker, DealBanner, DealsPage, DiscountInput, ElementsProvider, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderSummary, PaymentElement, Price, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductPage, ProductSheet, QuantitySelector, SaleBadge, SearchInput, SearchPage, ServiceCard, ServiceGrid, SlotPicker, StaffPicker, StoreNav, VariantSelector, cn, useAds, useAvailableSlots, useBookings, useBundles, useCart, useCategories, useCheckout, useCimplify, useCimplifyClient, useCollection, useCollections, useDeals, useElements, useElementsReady, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, useQuote, useSearch, useServiceAvailability, useServices, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount };
|