@misiki/gocommerce-connector 0.2.0 → 0.3.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.
@@ -165,7 +165,7 @@ function storedCartId() {
165
165
  if (!v || v === "undefined" || v === "null") return null;
166
166
  return v;
167
167
  }
168
- var rememberCartId = (id) => writeLocal("cart_id", id);
168
+ var rememberCartId = (id2) => writeLocal("cart_id", id2);
169
169
  var extrasKey = (cartId) => `gocommerce_cart_${cartId}`;
170
170
  var cartExtras = (cartId) => readJSON(extrasKey(cartId)) ?? {};
171
171
  function saveCartExtras(cartId, patch) {
@@ -233,7 +233,8 @@ var BaseService = class _BaseService {
233
233
  * as what it is rather than as a JSON parse error three frames away.
234
234
  */
235
235
  async engine(path, req = {}) {
236
- const url = this.engineURL(path, req.query);
236
+ const lang = (req.method ?? "GET") === "GET" ? this.creds.language : void 0;
237
+ const url = this.engineURL(path, lang ? { lang, ...req.query } : req.query);
237
238
  const headers = { Accept: "application/json", ...req.headers };
238
239
  if (req.body !== void 0) headers["Content-Type"] = "application/json";
239
240
  if (this.creds.store) headers["X-Store"] = this.creds.store;
@@ -456,6 +457,16 @@ function toVariant(v, p, optionless) {
456
457
  metadata: v.metadata ?? null
457
458
  };
458
459
  }
460
+ function attributesOf(p) {
461
+ const answers = p.metadata?.category;
462
+ if (!answers || typeof answers !== "object" || Array.isArray(answers)) return [];
463
+ return Object.entries(answers).flatMap(([handle, raw]) => {
464
+ const values = (Array.isArray(raw) ? raw : [raw]).map((v) => String(v ?? "").trim()).filter(Boolean);
465
+ if (!values.length) return [];
466
+ const name = handle.replace(/[-_]+/g, " ").replace(/^./, (c) => c.toUpperCase());
467
+ return [{ name, value: values.join(", "), handle }];
468
+ });
469
+ }
459
470
  function toProduct(p, extra = {}) {
460
471
  const live = liveVariants(p);
461
472
  const lead = live[0] ?? arr(p.variants)[0];
@@ -521,7 +532,7 @@ function toProduct(p, extra = {}) {
521
532
  rating: null,
522
533
  ratingCount: 0,
523
534
  reviewCount: 0,
524
- attributes: [],
535
+ attributes: attributesOf(p),
525
536
  metadata: p.metadata ?? null,
526
537
  createdAt: p.created_at,
527
538
  updatedAt: p.updated_at
@@ -1078,8 +1089,11 @@ var CatalogService = class extends BaseService {
1078
1089
  sorted
1079
1090
  };
1080
1091
  }
1092
+ const attr = Object.entries(q.attributes ?? {}).flatMap(
1093
+ ([handle, values]) => values.filter(Boolean).map((v) => `${handle}:${v}`)
1094
+ );
1081
1095
  const env = await this.engine("/api/products", {
1082
- query: { q: search, page, limit, channel: this.creds.channel }
1096
+ query: { q: search, page, limit, channel: this.creds.channel, attr }
1083
1097
  });
1084
1098
  return { ...toPage(env, (p) => toProduct(p)), categoryHierarchy: hierarchy, sorted };
1085
1099
  }
@@ -1214,6 +1228,16 @@ var num = (v) => {
1214
1228
  const n = Number(v);
1215
1229
  return Number.isFinite(n) ? n : void 0;
1216
1230
  };
1231
+ function attributesFrom(p) {
1232
+ const out = {};
1233
+ for (const [key, value] of p) {
1234
+ if (!key.startsWith("attributes.")) continue;
1235
+ const handle = key.slice("attributes.".length);
1236
+ const values = value.split(",").map((v) => decodeURIComponent(v).trim()).filter(Boolean);
1237
+ if (handle && values.length) out[handle] = [...out[handle] ?? [], ...values];
1238
+ }
1239
+ return Object.keys(out).length ? out : void 0;
1240
+ }
1217
1241
  var SearchService = class extends CatalogService {
1218
1242
  emptyResult() {
1219
1243
  return {
@@ -1235,7 +1259,8 @@ var SearchService = class extends CatalogService {
1235
1259
  sort: p.get("sort") ?? void 0,
1236
1260
  category: slug || p.get("categories")?.split(",")[0] || void 0,
1237
1261
  priceFrom: num(p.get("priceFrom")) ?? num(from ?? null),
1238
- priceTo: num(p.get("priceTo")) ?? num(to ?? null)
1262
+ priceTo: num(p.get("priceTo")) ?? num(to ?? null),
1263
+ attributes: attributesFrom(p)
1239
1264
  });
1240
1265
  return {
1241
1266
  data: res.data,
@@ -1367,10 +1392,10 @@ var REMOVE = -9999999;
1367
1392
  var CartService = class extends BaseService {
1368
1393
  facts = new StoreFacts(this._fetch);
1369
1394
  async getCartByCartId(cartId) {
1370
- const id = clean(cartId);
1371
- if (!id) return emptyCart(await this.currency());
1395
+ const id2 = clean(cartId);
1396
+ if (!id2) return emptyCart(await this.currency());
1372
1397
  try {
1373
- return await this.present(await this.data(`/api/carts/${encodeURIComponent(id)}`));
1398
+ return await this.present(await this.data(`/api/carts/${encodeURIComponent(id2)}`));
1374
1399
  } catch (err) {
1375
1400
  if (err instanceof GoCommerceError && err.status === 404) return emptyCart(await this.currency());
1376
1401
  throw err;
@@ -1385,9 +1410,9 @@ var CartService = class extends BaseService {
1385
1410
  * to clear the bag on the success page.
1386
1411
  */
1387
1412
  async refereshCart() {
1388
- const id = storedCartId();
1389
- const cart = await this.getCartByCartId(id);
1390
- if (id && cart.status === "converted") forgetCartExtras(id);
1413
+ const id2 = storedCartId();
1414
+ const cart = await this.getCartByCartId(id2);
1415
+ if (id2 && cart.status === "converted") forgetCartExtras(id2);
1391
1416
  return cart;
1392
1417
  }
1393
1418
  /**
@@ -1400,21 +1425,21 @@ var CartService = class extends BaseService {
1400
1425
  qty = 1,
1401
1426
  variantId
1402
1427
  }) {
1403
- let id = clean(cartId) ?? storedCartId();
1404
- if (lineId && id) {
1405
- const cart = await this.data(`/api/carts/${encodeURIComponent(id)}`);
1428
+ let id2 = clean(cartId) ?? storedCartId();
1429
+ if (lineId && id2) {
1430
+ const cart = await this.data(`/api/carts/${encodeURIComponent(id2)}`);
1406
1431
  const line = (cart.line_items ?? []).find((l) => String(l.id) === String(lineId));
1407
1432
  if (line) {
1408
1433
  const next = qty === REMOVE ? 0 : line.quantity + Number(qty);
1409
- return this.setQuantity(id, line.id, next);
1434
+ return this.setQuantity(id2, line.id, next);
1410
1435
  }
1411
1436
  }
1412
- if (qty === REMOVE || Number(qty) <= 0) return this.getCartByCartId(id);
1437
+ if (qty === REMOVE || Number(qty) <= 0) return this.getCartByCartId(id2);
1413
1438
  if (!variantId) throw new GoCommerceError("Please choose an option before adding this to your bag.", 400);
1414
- if (!id) id = await this.open();
1439
+ if (!id2) id2 = await this.open();
1415
1440
  try {
1416
1441
  return await this.present(
1417
- await this.data(`/api/carts/${encodeURIComponent(id)}/line-items`, {
1442
+ await this.data(`/api/carts/${encodeURIComponent(id2)}/line-items`, {
1418
1443
  method: "POST",
1419
1444
  body: { variant_id: Number(variantId), quantity: Number(qty) }
1420
1445
  })
@@ -1438,15 +1463,15 @@ var CartService = class extends BaseService {
1438
1463
  lineId,
1439
1464
  qty
1440
1465
  }) {
1441
- const id = clean(cartId) ?? storedCartId();
1442
- if (!id) return emptyCart(await this.currency());
1443
- return this.setQuantity(id, lineId, Number(qty));
1466
+ const id2 = clean(cartId) ?? storedCartId();
1467
+ if (!id2) return emptyCart(await this.currency());
1468
+ return this.setQuantity(id2, lineId, Number(qty));
1444
1469
  }
1445
1470
  async removeCart({ cartId, lineId }) {
1446
- const id = clean(cartId) ?? storedCartId();
1447
- if (!id) return emptyCart(await this.currency());
1471
+ const id2 = clean(cartId) ?? storedCartId();
1472
+ if (!id2) return emptyCart(await this.currency());
1448
1473
  return this.present(
1449
- await this.data(`/api/carts/${encodeURIComponent(id)}/line-items/${encodeURIComponent(String(lineId))}`, {
1474
+ await this.data(`/api/carts/${encodeURIComponent(id2)}/line-items/${encodeURIComponent(String(lineId))}`, {
1450
1475
  method: "DELETE"
1451
1476
  })
1452
1477
  );
@@ -1457,8 +1482,8 @@ var CartService = class extends BaseService {
1457
1482
  * and the rest is kept for the checkout request.
1458
1483
  */
1459
1484
  async updateCart2(args) {
1460
- const id = clean(args.cartId) ?? storedCartId();
1461
- if (!id) return emptyCart(await this.currency());
1485
+ const id2 = clean(args.cartId) ?? storedCartId();
1486
+ if (!id2) return emptyCart(await this.currency());
1462
1487
  const patch = {};
1463
1488
  if (args.phone !== void 0) patch.phone = args.phone || void 0;
1464
1489
  if (args.shippingAddress !== void 0) {
@@ -1468,20 +1493,20 @@ var CartService = class extends BaseService {
1468
1493
  if (args.billingAddress !== void 0 || args.isBillingAddressSameAsShipping) {
1469
1494
  patch.billingAddress = args.isBillingAddressSameAsShipping ? null : args.billingAddress ?? null;
1470
1495
  }
1471
- const before = cartExtras(id);
1496
+ const before = cartExtras(id2);
1472
1497
  const country = (a) => String(a?.countryCode || a?.country || "").toUpperCase();
1473
1498
  if (args.shippingAddress !== void 0 && country(args.shippingAddress) !== country(before.shippingAddress)) {
1474
1499
  patch.shippingRateId = null;
1475
1500
  patch.shippingRate = null;
1476
1501
  }
1477
- saveCartExtras(id, patch);
1502
+ saveCartExtras(id2, patch);
1478
1503
  const email = args.email ?? (args.shippingAddress?.email ? String(args.shippingAddress.email) : void 0);
1479
1504
  if (email !== void 0) {
1480
1505
  return this.present(
1481
- await this.data(`/api/carts/${encodeURIComponent(id)}/email`, { method: "PUT", body: { email } })
1506
+ await this.data(`/api/carts/${encodeURIComponent(id2)}/email`, { method: "PUT", body: { email } })
1482
1507
  );
1483
1508
  }
1484
- return this.getCartByCartId(id);
1509
+ return this.getCartByCartId(id2);
1485
1510
  }
1486
1511
  /**
1487
1512
  * A discount code. The engine prices it against the basket now and decides
@@ -1489,34 +1514,34 @@ var CartService = class extends BaseService {
1489
1514
  * because the cart itself does not carry the code.
1490
1515
  */
1491
1516
  async applyCoupon({ cartId, couponCode }) {
1492
- const id = clean(cartId) ?? storedCartId();
1493
- if (!id) throw new GoCommerceError("Add something to your bag before applying a code.", 400);
1494
- const cart = await this.data(`/api/carts/${encodeURIComponent(id)}`);
1495
- const discount = await this.data(`/api/carts/${encodeURIComponent(id)}/discount`, {
1517
+ const id2 = clean(cartId) ?? storedCartId();
1518
+ if (!id2) throw new GoCommerceError("Add something to your bag before applying a code.", 400);
1519
+ const cart = await this.data(`/api/carts/${encodeURIComponent(id2)}`);
1520
+ const discount = await this.data(`/api/carts/${encodeURIComponent(id2)}/discount`, {
1496
1521
  method: "PUT",
1497
1522
  body: { code: String(couponCode ?? "").trim(), ...cart.email ? { email: cart.email } : {} }
1498
1523
  });
1499
- saveCartExtras(id, { discount: { ...discount, code: discount.code || String(couponCode).trim() } });
1524
+ saveCartExtras(id2, { discount: { ...discount, code: discount.code || String(couponCode).trim() } });
1500
1525
  return this.present(cart);
1501
1526
  }
1502
1527
  async removeCoupon(cartId) {
1503
- const id = clean(cartId) ?? storedCartId();
1504
- if (!id) return emptyCart(await this.currency());
1505
- await this.engine(`/api/carts/${encodeURIComponent(id)}/discount`, { method: "DELETE" });
1506
- saveCartExtras(id, { discount: null });
1507
- return this.getCartByCartId(id);
1528
+ const id2 = clean(cartId) ?? storedCartId();
1529
+ if (!id2) return emptyCart(await this.currency());
1530
+ await this.engine(`/api/carts/${encodeURIComponent(id2)}/discount`, { method: "DELETE" });
1531
+ saveCartExtras(id2, { discount: null });
1532
+ return this.getCartByCartId(id2);
1508
1533
  }
1509
1534
  /** The delivery option the shopper picked, priced from the engine's own quote. */
1510
1535
  async updateShippingRate({ cartId, shippingRateId }) {
1511
- const id = clean(cartId) ?? storedCartId();
1512
- if (!id) return emptyCart(await this.currency());
1513
- const ship = cartExtras(id).shippingAddress;
1536
+ const id2 = clean(cartId) ?? storedCartId();
1537
+ if (!id2) return emptyCart(await this.currency());
1538
+ const ship = cartExtras(id2).shippingAddress;
1514
1539
  const country = String(ship?.countryCode || ship?.country || "").toUpperCase();
1515
1540
  if (!country) throw new GoCommerceError("Add a delivery address first.", 400);
1516
- const quote2 = await this.data("/api/checkout/rates", { query: { cart: id, country, state: ship?.state ? String(ship.state) : void 0 } });
1541
+ const quote2 = await this.data("/api/checkout/rates", { query: { cart: id2, country, state: ship?.state ? String(ship.state) : void 0 } });
1517
1542
  const rate = (quote2.rates ?? []).find((r) => String(r.rate_id) === String(shippingRateId));
1518
1543
  if (!rate) throw new GoCommerceError("That delivery option is not available for this address any more.", 409);
1519
- saveCartExtras(id, {
1544
+ saveCartExtras(id2, {
1520
1545
  shippingRateId: String(rate.rate_id),
1521
1546
  shippingRate: {
1522
1547
  id: String(rate.rate_id),
@@ -1525,7 +1550,7 @@ var CartService = class extends BaseService {
1525
1550
  currency: rate.price.currency
1526
1551
  }
1527
1552
  });
1528
- return this.getCartByCartId(id);
1553
+ return this.getCartByCartId(id2);
1529
1554
  }
1530
1555
  /** The payment method is chosen at checkout, by which checkout route is called. */
1531
1556
  async updateCartPaymentMethod({ cartId }) {
@@ -1571,25 +1596,25 @@ var CartService = class extends BaseService {
1571
1596
  return { slug: p.slug, thumbnail: v?.image?.url ?? p.image_url ?? null };
1572
1597
  });
1573
1598
  }
1574
- productFor(id) {
1599
+ productFor(id2) {
1575
1600
  return remember(
1576
- `${this.engineBase()}|product|${id}`,
1577
- () => this.data(`/api/products/${id}`).catch(() => null)
1601
+ `${this.engineBase()}|product|${id2}`,
1602
+ () => this.data(`/api/products/${id2}`).catch(() => null)
1578
1603
  );
1579
1604
  }
1580
1605
  };
1581
- var clean = (id) => id && id !== "undefined" && id !== "null" ? id : null;
1606
+ var clean = (id2) => id2 && id2 !== "undefined" && id2 !== "null" ? id2 : null;
1582
1607
  var cartService = new CartService();
1583
1608
 
1584
1609
  // src/services/checkout-service.ts
1585
1610
  var CheckoutService = class extends BaseService {
1586
1611
  /** Delivery options for the address on the cart: `{ data: [{ id, name, base_rate }] }`. */
1587
1612
  async getShippingRates({ cartId } = {}) {
1588
- const id = cartId || storedCartId();
1589
- const ship = id ? cartExtras(id).shippingAddress : null;
1613
+ const id2 = cartId || storedCartId();
1614
+ const ship = id2 ? cartExtras(id2).shippingAddress : null;
1590
1615
  const country = String(ship?.countryCode || ship?.country || "").toUpperCase();
1591
- if (!id || !country) return { data: [] };
1592
- const quote2 = await this.data("/api/checkout/rates", { query: { cart: id, country, state: ship?.state ? String(ship.state) : void 0 } });
1616
+ if (!id2 || !country) return { data: [] };
1617
+ const quote2 = await this.data("/api/checkout/rates", { query: { cart: id2, country, state: ship?.state ? String(ship.state) : void 0 } });
1593
1618
  return {
1594
1619
  data: (quote2.rates ?? []).map((r) => ({
1595
1620
  id: String(r.rate_id),
@@ -1643,10 +1668,11 @@ var CheckoutService = class extends BaseService {
1643
1668
  if (!order) throw new GoCommerceError("That payment does not belong to an order placed from this browser.", 404);
1644
1669
  return { order_no: order.number, status: "processing" };
1645
1670
  }
1646
- // The family's other gateways. GoCommerce's Stripe is a Payment Element
1647
- // (a client secret, not a hosted page), and PayPal, Cashfree, PhonePe and
1648
- // Affirm are not engine gateways at all; paymentMethodService never lists
1649
- // them, so these are reached only by a storefront that hard-codes one.
1671
+ // The family's other gateway flows. GoCommerce's Stripe is a Payment
1672
+ // Element (a client secret, not a hosted page) — `checkout('stripe')` returns
1673
+ // that secret for a storefront that mounts the element. Cashfree, PhonePe and
1674
+ // Affirm are not engine gateways at all. paymentMethodService never offers
1675
+ // these codes, so they are reached only by a storefront that hard-codes one.
1650
1676
  async checkoutStripe() {
1651
1677
  throw unsupported(
1652
1678
  "checkoutStripe",
@@ -1656,11 +1682,34 @@ var CheckoutService = class extends BaseService {
1656
1682
  async checkoutStripeCapture() {
1657
1683
  throw unsupported("checkoutStripeCapture", "Stripe confirms payments to the engine by webhook");
1658
1684
  }
1659
- async checkoutPaypal() {
1660
- throw unsupported("checkoutPaypal", "the engine has no PayPal gateway");
1685
+ /**
1686
+ * The storefront's generic redirect flow, which it reaches through the code
1687
+ * `PAYPAL`. The engine has no PayPal gateway; paymentMethodService puts the
1688
+ * store's first hosted-page gateway — Adyen, Paddle, Lemon Squeezy, Creem,
1689
+ * Hyperswitch or RevenueCat — under that code, with its own name and a
1690
+ * neutral icon, because this is the one storefront flow that sends a shopper
1691
+ * to a URL and back. The shopper returns to the success page; the gateway's
1692
+ * webhook tells the engine the order is paid.
1693
+ */
1694
+ async checkoutPaypal({ cartId, origin } = {}) {
1695
+ const gateway = await this.redirectGateway();
1696
+ if (!gateway) throw unsupported("checkoutPaypal", "this store has no hosted-page payment gateway switched on");
1697
+ const id2 = cartId || storedCartId() || "";
1698
+ const back = origin ? `${origin.replace(/\/+$/, "")}/checkout/success?cart_id=${encodeURIComponent(id2)}` : void 0;
1699
+ const result = await this.place(gateway, id2, back ? { return_url: back } : {});
1700
+ const url = result.payment.client_data?.url;
1701
+ if (!url) throw new GoCommerceError("The payment page did not open. Please try again.", 502);
1702
+ return { redirect_url: url, order_no: result.order.number };
1661
1703
  }
1662
- async capturePaypalPayment() {
1663
- throw unsupported("capturePaypalPayment", "the engine has no PayPal gateway");
1704
+ /**
1705
+ * Nothing to capture: a hosted-page gateway confirms the payment to the
1706
+ * engine by webhook. Answering, rather than throwing, sends the storefront's
1707
+ * process page on to the success page instead of the failure page.
1708
+ */
1709
+ async capturePaypalPayment({
1710
+ order_no
1711
+ } = {}) {
1712
+ return { order_no: order_no ?? null, status: "processing" };
1664
1713
  }
1665
1714
  async checkoutCashfree() {
1666
1715
  throw unsupported("checkoutCashfree", "the engine has no Cashfree gateway");
@@ -1683,6 +1732,57 @@ var CheckoutService = class extends BaseService {
1683
1732
  async checkoutPOS() {
1684
1733
  throw unsupported("checkoutPOS", "point-of-sale orders are placed from the admin panel");
1685
1734
  }
1735
+ /**
1736
+ * Any payment method the engine has switched on, by its engine code —
1737
+ * `cod`, `stripe`, `razorpay`, `adyen`, `paddle`, `helcim`… For a storefront
1738
+ * that drives the payment itself: the answer says what to do next.
1739
+ *
1740
+ * - `kind: 'none'` — nothing to pay now (cash on delivery).
1741
+ * - `kind: 'client_action'` — mount the gateway's own widget with
1742
+ * `clientData`: Stripe's Payment Element takes `clientSecret`, Razorpay's
1743
+ * modal `razorpay_order_id` and `key_id`, Helcim's `checkout_token`.
1744
+ * - `kind: 'redirect'` — send the shopper to `redirectUrl`; `returnUrl` is
1745
+ * where the gateway brings them back.
1746
+ *
1747
+ * Either way the gateway's webhook tells the engine when the money arrives;
1748
+ * read `orderService.getOrder(orderNo).paymentStatus` to see it.
1749
+ */
1750
+ async checkout(code, {
1751
+ cartId,
1752
+ returnUrl,
1753
+ paymentData,
1754
+ metadata
1755
+ } = {}) {
1756
+ const result = await this.place(String(code).toLowerCase(), cartId, {
1757
+ ...returnUrl ? { return_url: returnUrl } : {},
1758
+ ...paymentData ? { payment_data: paymentData } : {},
1759
+ ...metadata ? { metadata } : {}
1760
+ });
1761
+ const data = result.payment.client_data ?? {};
1762
+ return {
1763
+ orderNo: result.order.number,
1764
+ order: toOrder(result.order),
1765
+ payment: {
1766
+ kind: result.payment.kind,
1767
+ provider: result.payment.provider,
1768
+ reference: result.payment.reference ?? null,
1769
+ clientSecret: data.client_secret ?? null,
1770
+ redirectUrl: data.url ?? null,
1771
+ clientData: data
1772
+ }
1773
+ };
1774
+ }
1775
+ /** Every payment method the engine has switched on, by engine code and name — drivable here or not. */
1776
+ async listMethods() {
1777
+ const options = await this.data(
1778
+ "/api/checkout"
1779
+ );
1780
+ return { methods: options.methods ?? [], currency: options.currency };
1781
+ }
1782
+ async redirectGateway() {
1783
+ const { methods } = await this.listMethods();
1784
+ return methods.find((m) => REDIRECT_GATEWAYS.has(m.code))?.code;
1785
+ }
1686
1786
  /**
1687
1787
  * One checkout attempt.
1688
1788
  *
@@ -1693,27 +1793,28 @@ var CheckoutService = class extends BaseService {
1693
1793
  * request — a different address, a different method — gets a new key, since
1694
1794
  * the engine refuses a key replayed with a different body.
1695
1795
  */
1696
- async place(code, cartId) {
1697
- const id = cartId || storedCartId();
1698
- if (!id) throw new GoCommerceError("Your bag is empty.", 400);
1699
- const extras = cartExtras(id);
1796
+ async place(code, cartId, extra = {}) {
1797
+ const id2 = cartId || storedCartId();
1798
+ if (!id2) throw new GoCommerceError("Your bag is empty.", 400);
1799
+ const extras = cartExtras(id2);
1700
1800
  const ship = extras.shippingAddress;
1701
1801
  if (!ship) throw new GoCommerceError("Add a delivery address first.", 400);
1702
- const cart = await this.data(`/api/carts/${encodeURIComponent(id)}`);
1802
+ const cart = await this.data(`/api/carts/${encodeURIComponent(id2)}`);
1703
1803
  const email = cart.email || (ship.email ? String(ship.email) : "");
1704
1804
  if (!email) throw new GoCommerceError("Add an email address so we can send your receipt.", 400);
1705
1805
  const address = toEngineAddress(ship);
1706
1806
  const body = {
1707
- cart_id: id,
1807
+ cart_id: id2,
1708
1808
  email,
1709
1809
  ...extras.phone || address.phone ? { phone: String(extras.phone || address.phone) } : {},
1710
1810
  ...address.name ? { name: address.name } : {},
1711
1811
  address,
1712
- ...extras.shippingRateId ? { shipping_rate_id: Number(extras.shippingRateId) } : {}
1812
+ ...extras.shippingRateId ? { shipping_rate_id: Number(extras.shippingRateId) } : {},
1813
+ ...extra
1713
1814
  };
1714
1815
  const serialised = JSON.stringify(body);
1715
1816
  const key = extras.attempt?.body === `${code}:${serialised}` ? extras.attempt.key : newKey();
1716
- saveCartExtras(id, { attempt: { key, body: `${code}:${serialised}` } });
1817
+ saveCartExtras(id2, { attempt: { key, body: `${code}:${serialised}` } });
1717
1818
  const result = await this.data(`/api/checkout/${encodeURIComponent(code)}`, {
1718
1819
  method: "POST",
1719
1820
  body,
@@ -1723,7 +1824,7 @@ var CheckoutService = class extends BaseService {
1723
1824
  rememberOrder({
1724
1825
  number: result.order.number,
1725
1826
  token: result.order.access_token,
1726
- cartId: id,
1827
+ cartId: id2,
1727
1828
  reference: result.payment.client_data?.razorpay_order_id ?? result.payment.reference
1728
1829
  });
1729
1830
  if (sessionToken()) {
@@ -1734,10 +1835,11 @@ var CheckoutService = class extends BaseService {
1734
1835
  }).catch(() => void 0);
1735
1836
  }
1736
1837
  }
1737
- saveCartExtras(id, { attempt: null });
1838
+ saveCartExtras(id2, { attempt: null });
1738
1839
  return result;
1739
1840
  }
1740
1841
  };
1842
+ var REDIRECT_GATEWAYS = /* @__PURE__ */ new Set(["adyen", "paddle", "lemonsqueezy", "creem", "hyperswitch", "revenuecat"]);
1741
1843
  var unsupported = (method, why) => new NotSupportedError("CheckoutService", method, why);
1742
1844
  function newKey() {
1743
1845
  const c = globalThis.crypto;
@@ -1757,23 +1859,25 @@ var PaymentMethodService = class extends BaseService {
1757
1859
  async list(_args = {}) {
1758
1860
  const options = await this.facts.checkoutOptions();
1759
1861
  const methods = options.methods ?? [];
1862
+ const redirect = methods.find((m) => REDIRECT_GATEWAYS.has(m.code))?.code;
1863
+ const drive = (code) => DRIVABLE[code] ?? (code === redirect ? { code: "PAYPAL", img: "/payment/card.svg" } : void 0);
1760
1864
  const data = methods.filter((m) => {
1761
- if (DRIVABLE[m.code]) return true;
1865
+ if (drive(m.code)) return true;
1762
1866
  if (!reported2.has(m.code)) {
1763
1867
  reported2.add(m.code);
1764
1868
  console.warn(
1765
- `[gocommerce] payment method "${m.code}" is switched on but this storefront cannot drive its flow, so it is not offered.`
1869
+ `[gocommerce] payment method "${m.code}" is switched on but this storefront cannot drive its flow, so it is not offered here; checkoutService.checkout('${m.code}') still reaches it.`
1766
1870
  );
1767
1871
  }
1768
1872
  return false;
1769
1873
  }).map((m, i) => ({
1770
1874
  id: m.code,
1771
- code: DRIVABLE[m.code].code,
1875
+ code: drive(m.code).code,
1772
1876
  engineCode: m.code,
1773
1877
  name: m.name,
1774
1878
  title: m.name,
1775
1879
  description: "",
1776
- img: DRIVABLE[m.code].img ?? null,
1880
+ img: drive(m.code).img ?? null,
1777
1881
  rank: i,
1778
1882
  active: true,
1779
1883
  isActive: true
@@ -1944,6 +2048,42 @@ var UserService = class extends BaseService {
1944
2048
  startSession(auth);
1945
2049
  return toUser(auth.record);
1946
2050
  }
2051
+ /**
2052
+ * Mails the signed-in shopper a link to confirm their address. The engine
2053
+ * sends none at signup, and a confirmed address is what customer-group
2054
+ * prices are keyed on. 409 once confirmed; 429 within a minute of the last.
2055
+ */
2056
+ async requestEmailVerification() {
2057
+ return this.account("/x/identity/me/email-verification", { method: "POST", auth: true });
2058
+ }
2059
+ /** Extends the session another thirty days. The token stays the same. */
2060
+ async refreshSession() {
2061
+ return startSession(await this.account("/x/identity/refresh", { method: "POST", auth: true }));
2062
+ }
2063
+ /**
2064
+ * Files a guest order under the signed-in account, so it appears in their
2065
+ * order history. The order's access token is the proof of ownership: pass
2066
+ * it, or leave it out for an order placed from this browser, whose token was
2067
+ * kept at checkout.
2068
+ *
2069
+ * Never done automatically at sign-in. On a shared computer that would file
2070
+ * one person's orders — address and all — under whoever signed in next.
2071
+ */
2072
+ async claimOrder({ number, token }) {
2073
+ const proof = token ?? placedOrder(number)?.token;
2074
+ if (!proof) {
2075
+ throw new GoCommerceError(
2076
+ "This order was not placed from this browser; enter the code from its confirmation.",
2077
+ 400
2078
+ );
2079
+ }
2080
+ const order = await this.account("/x/identity/me/orders", {
2081
+ method: "POST",
2082
+ auth: true,
2083
+ body: { number: String(number), token: proof }
2084
+ });
2085
+ return toOrder(order);
2086
+ }
1947
2087
  async deleteUser() {
1948
2088
  throw new NotSupportedError("UserService", "deleteUser", "an account is closed by the store, on request");
1949
2089
  }
@@ -2041,16 +2181,16 @@ var AddressService = class extends BaseService {
2041
2181
  page: env.meta?.page ?? page
2042
2182
  };
2043
2183
  }
2044
- async fetchAddress(id) {
2184
+ async fetchAddress(id2) {
2045
2185
  return fromIdentityAddress(
2046
- await this.data(`/x/identity/me/addresses/${enc(id)}`, { auth: true }),
2186
+ await this.data(`/x/identity/me/addresses/${enc(id2)}`, { auth: true }),
2047
2187
  currentMe()?.email
2048
2188
  );
2049
2189
  }
2050
2190
  /** New when it has no id (or the form's `'new'`), an edit when it has one — the storefront saves both here. */
2051
2191
  async saveAddress(address) {
2052
- const id = address?.id && address.id !== "new" ? String(address.id) : null;
2053
- if (id) return this.editAddress(id, address);
2192
+ const id2 = address?.id && address.id !== "new" ? String(address.id) : null;
2193
+ if (id2) return this.editAddress(id2, address);
2054
2194
  const a = await this.data("/x/identity/me/addresses", {
2055
2195
  method: "POST",
2056
2196
  auth: true,
@@ -2058,20 +2198,20 @@ var AddressService = class extends BaseService {
2058
2198
  });
2059
2199
  return fromIdentityAddress(a, currentMe()?.email);
2060
2200
  }
2061
- async editAddress(id, address) {
2062
- const a = await this.data(`/x/identity/me/addresses/${enc(id)}`, {
2201
+ async editAddress(id2, address) {
2202
+ const a = await this.data(`/x/identity/me/addresses/${enc(id2)}`, {
2063
2203
  method: "PATCH",
2064
2204
  auth: true,
2065
2205
  body: toIdentityAddress(address)
2066
2206
  });
2067
2207
  return fromIdentityAddress(a, currentMe()?.email);
2068
2208
  }
2069
- async deleteAddress(id) {
2070
- await this.engine(`/x/identity/me/addresses/${enc(id)}`, { method: "DELETE", auth: true });
2071
- return { id: String(id), deleted: true };
2209
+ async deleteAddress(id2) {
2210
+ await this.engine(`/x/identity/me/addresses/${enc(id2)}`, { method: "DELETE", auth: true });
2211
+ return { id: String(id2), deleted: true };
2072
2212
  }
2073
2213
  };
2074
- var enc = (id) => encodeURIComponent(String(id));
2214
+ var enc = (id2) => encodeURIComponent(String(id2));
2075
2215
  var addressService = new AddressService();
2076
2216
 
2077
2217
  // src/services/wishlist-service.ts
@@ -2123,6 +2263,18 @@ var WishlistService = class extends BaseService {
2123
2263
  const saved = new Set((list?.items ?? []).map((i) => String(i.product_id)));
2124
2264
  return (pairs ?? []).map((p) => ({ ...p, exists: saved.has(String(p.productId)) }));
2125
2265
  }
2266
+ /**
2267
+ * The address the store may write to when a saved product is back in stock
2268
+ * or on sale; `''` takes it off. A list that does not exist yet is made.
2269
+ */
2270
+ async setEmail(email) {
2271
+ const list = await this.current() ?? await this.create();
2272
+ await this.data(`/x/wishlist/${enc2(list.token)}`, {
2273
+ method: "PATCH",
2274
+ body: { email: String(email ?? "").trim() }
2275
+ });
2276
+ return { email: String(email ?? "").trim() || null };
2277
+ }
2126
2278
  async current() {
2127
2279
  const token = wishlistToken();
2128
2280
  if (!token) return null;
@@ -2205,6 +2357,15 @@ var MenuService = class extends BaseService {
2205
2357
  if (menus) return { data: menus };
2206
2358
  return resolveRestLocally("get", "/api/menu");
2207
2359
  }
2360
+ /** Every menu the store keeps, by handle — not only the header and footer. */
2361
+ async listMenus() {
2362
+ return await this.data("/x/navigation/menus") ?? [];
2363
+ }
2364
+ /** One menu by handle, in the storefront's shape: `{ menuId, name, items: [{ id, name, link, items }] }`. */
2365
+ async getMenu(handle) {
2366
+ const m = await this.data(`/x/navigation/menus/${encodeURIComponent(handle)}`);
2367
+ return { menuId: m.handle, name: m.title, items: (m.items ?? []).map(toItem) };
2368
+ }
2208
2369
  /** The header and footer menus, or `null` when the module is not there. Cached: a menu is store-level. */
2209
2370
  async engineMenus() {
2210
2371
  if (!await this.facts.pluginOn("navigation")) return null;
@@ -2226,6 +2387,24 @@ var readStaticStore = async () => {
2226
2387
  return {};
2227
2388
  };
2228
2389
 
2390
+ // src/services/newsletter-service.ts
2391
+ var NewsletterService = class extends BaseService {
2392
+ async subscribe({ email, name, source }) {
2393
+ return this.data("/x/newsletter/subscribe", {
2394
+ method: "POST",
2395
+ body: { email: String(email ?? "").trim(), ...name ? { name } : {}, ...source ? { source } : {} }
2396
+ });
2397
+ }
2398
+ /** The token from the unsubscribe link in a newsletter email. */
2399
+ async unsubscribe(token) {
2400
+ return this.data("/x/newsletter/unsubscribe", {
2401
+ method: "POST",
2402
+ body: { token: String(token ?? "") }
2403
+ });
2404
+ }
2405
+ };
2406
+ var newsletterService = new NewsletterService();
2407
+
2229
2408
  // src/services/store-service.ts
2230
2409
  function symbolFor(code) {
2231
2410
  try {
@@ -2259,6 +2438,22 @@ var StoreService = class extends BaseService {
2259
2438
  }
2260
2439
  return super.get(path);
2261
2440
  }
2441
+ /**
2442
+ * The storefront's newsletter box posts `{ email }` to Litekart's
2443
+ * `/api/newsletter/subscribe` through this verb; ext/newsletter is where
2444
+ * that list lives here.
2445
+ */
2446
+ async post(path, data) {
2447
+ if (path.split("?")[0] === "/api/newsletter/subscribe") {
2448
+ const { email, name } = data ?? {};
2449
+ return await new NewsletterService(this._fetch).subscribe({
2450
+ email: email ?? "",
2451
+ name,
2452
+ source: "storefront"
2453
+ });
2454
+ }
2455
+ return super.post(path, data);
2456
+ }
2262
2457
  async live() {
2263
2458
  const [ready, plugins, menus, base] = await Promise.all([
2264
2459
  this.facts.ready(),
@@ -2564,6 +2759,300 @@ var varniCustomProductService = new VarniCustomProductService();
2564
2759
  var pluginsService = pluginService;
2565
2760
  var settingsService = settingService;
2566
2761
 
2567
- export { AddressService, AuthService, AutocompleteService, BannerService, BaseService, BlogService, CartService, CatalogService, CategoryService, ChatService, CheckoutService, CollectionService, ContactService, CountryService, CouponService, CurrencyService, DealService, DemoRequestService, EnquiryService, FaqService, FeedbackService, GalleryService, HomeService, InitService, MeilisearchService, MenuService, OrderService, PageService, PaymentMethodService, PluginService, PluginService as PluginsService, PopularSearchService, PopularityService, ProductService, ProfileService, ReelsService, RegionService, ReviewService, SearchService, SettingService, SettingService as SettingsService, StateService, StoreService, UploadService, UserService, VarniCustomDesignService, VarniCustomProductService, VendorService, WarrantyService, WishlistService, addressService, authService, autocompleteService, bannerService, blogService, cartService, categoryService, chatService, checkoutService, collectionService, contactService, countryService, couponService, currencyService, dealService, demoRequestService, emptyPage, enquiryService, faqService, feedbackService, galleryService, homeService, initService, meilisearchService, menuService, orderService, pageService, paymentMethodService, pluginService, pluginsService, popularSearchService, popularityService, productService, profileService, qs, reelsService, regionService, reviewService, searchService, settingService, settingsService, stateService, staticStore, storeService, toPage, uploadService, userService, varniCustomDesignService, varniCustomProductService, vendorService, warrantyService, wishlistService };
2762
+ // src/services/variant-service.ts
2763
+ var VariantService = class extends BaseService {
2764
+ /** A product's variants, inactive ones included (filter on `active`), in the merchant's order. */
2765
+ async list(productId, { page = 1, limit = 50 } = {}) {
2766
+ const env = await this.engine("/api/variants", { query: { product_id: productId, page, limit } });
2767
+ return { data: (env.data ?? []).map(withAmounts), meta: env.meta };
2768
+ }
2769
+ async getOne(id2) {
2770
+ return withAmounts(await this.data(`/api/variants/${encodeURIComponent(String(id2))}`));
2771
+ }
2772
+ /**
2773
+ * What each approved seller asks for this variant, cheapest first. A cart
2774
+ * line takes a variant, not an offer, so this informs a shopper rather than
2775
+ * choosing who fulfils.
2776
+ */
2777
+ async offers(variantId) {
2778
+ const offers = await this.data(`/api/variants/${encodeURIComponent(String(variantId))}/offers`) ?? [];
2779
+ return offers.map((o) => ({ ...o, price: { ...o.price, amount: toMajor(o.price) } }));
2780
+ }
2781
+ };
2782
+ function withAmounts(v) {
2783
+ return {
2784
+ ...v,
2785
+ price: { ...v.price, amount: toMajor(v.price) },
2786
+ compare_at_price: v.compare_at_price ? { ...v.compare_at_price, amount: toMajor(v.compare_at_price) } : void 0
2787
+ };
2788
+ }
2789
+ var variantService = new VariantService();
2790
+
2791
+ // src/services/b2b-service.ts
2792
+ var B2bService = class extends BaseService {
2793
+ // ------------------------------------------------------------ company
2794
+ /** The company, the caller's role in it, and its credit: limit, outstanding, available, overdue. */
2795
+ async me() {
2796
+ const me = await this.data("/x/b2b/me", { auth: true });
2797
+ const { notes: _notes, metadata: _metadata, ...company } = me.company;
2798
+ return { company, role: me.role, credit: me.credit };
2799
+ }
2800
+ async members() {
2801
+ return await this.data("/x/b2b/members", { auth: true }) ?? [];
2802
+ }
2803
+ /** Admin only. Demoting the last admin is refused (409). */
2804
+ async setMemberRole(customerId, role) {
2805
+ return this.data(`/x/b2b/members/${id(customerId)}`, { method: "PATCH", auth: true, body: { role } });
2806
+ }
2807
+ /** An admin removes anyone; any member may remove themselves. */
2808
+ async removeMember(customerId) {
2809
+ await this.engine(`/x/b2b/members/${id(customerId)}`, { method: "DELETE", auth: true });
2810
+ }
2811
+ // ------------------------------------------------------------ invitations
2812
+ /** Admin only. Open invitations: not accepted, not expired. */
2813
+ async invitations() {
2814
+ return await this.data("/x/b2b/invitations", { auth: true }) ?? [];
2815
+ }
2816
+ /** Admin only. Emails a link; a second invitation to the same address replaces the first. */
2817
+ async invite({ email, role }) {
2818
+ return this.data("/x/b2b/invitations", {
2819
+ method: "POST",
2820
+ auth: true,
2821
+ body: { email: String(email ?? "").trim(), role }
2822
+ });
2823
+ }
2824
+ async withdrawInvitation(invitationId) {
2825
+ await this.engine(`/x/b2b/invitations/${id(invitationId)}`, { method: "DELETE", auth: true });
2826
+ }
2827
+ /**
2828
+ * The token from the invitation email, accepted by the account it was sent
2829
+ * to — which also confirms that account's email address.
2830
+ */
2831
+ async acceptInvitation(token) {
2832
+ return this.data("/x/b2b/invitations/accept", {
2833
+ method: "POST",
2834
+ auth: true,
2835
+ body: { token: String(token ?? "").trim() }
2836
+ });
2837
+ }
2838
+ // ------------------------------------------------------------ filling a basket
2839
+ /**
2840
+ * A quick order: up to 500 lines by SKU or variant id, into the bag (or a new
2841
+ * one), at the company's prices. A line that will not go in is reported in
2842
+ * `rejected` with its reason; the rest are added.
2843
+ */
2844
+ async addLines({
2845
+ cartId,
2846
+ lines
2847
+ }) {
2848
+ const res = await this.data("/x/b2b/cart/lines", {
2849
+ method: "POST",
2850
+ auth: true,
2851
+ body: {
2852
+ ...cartId ?? storedCartId() ? { cart_id: cartId ?? storedCartId() } : {},
2853
+ lines: (lines ?? []).map((l) => ({
2854
+ ...l.variantId ? { variant_id: Number(l.variantId) } : {},
2855
+ ...l.sku ? { sku: String(l.sku).trim() } : {},
2856
+ quantity: Number(l.quantity)
2857
+ }))
2858
+ }
2859
+ });
2860
+ rememberCartId(res.cart.id);
2861
+ return { cart: toCart(res.cart, cartExtras(res.cart.id)), rejected: res.rejected ?? [] };
2862
+ }
2863
+ /** A past order's lines in a new bag, at today's prices. Nothing is placed. */
2864
+ async reorder(orderId) {
2865
+ const res = await this.data(
2866
+ `/x/b2b/orders/${id(orderId)}/reorder`,
2867
+ {
2868
+ method: "POST",
2869
+ auth: true
2870
+ }
2871
+ );
2872
+ rememberCartId(res.cart.id);
2873
+ return { cart: toCart(res.cart, cartExtras(res.cart.id)), rejected: res.rejected ?? [] };
2874
+ }
2875
+ // ------------------------------------------------------------ placing an order
2876
+ /**
2877
+ * Places the bag through the company: on account by default, with a PO
2878
+ * number, optionally only some lines (`lineIds`). A buyer over the company's
2879
+ * approval threshold gets `{ kind: 'approval' }` instead of an order — the
2880
+ * bag stays as it was, and an approver places it later.
2881
+ *
2882
+ * Over the credit limit is 403 `credit_limit_exceeded`; `me().credit` says
2883
+ * how much is available before the shopper tries.
2884
+ */
2885
+ async checkout({
2886
+ cartId,
2887
+ lineIds,
2888
+ shippingRateId,
2889
+ returnUrl,
2890
+ paymentData,
2891
+ ...opts
2892
+ } = {}) {
2893
+ const cart = cartId || storedCartId();
2894
+ if (!cart) throw new GoCommerceError("Your bag is empty.", 400);
2895
+ const extras = cartExtras(cart);
2896
+ const rate = shippingRateId ?? extras.shippingRateId;
2897
+ const body = {
2898
+ cart_id: cart,
2899
+ ...this.placeBody(opts, extras.shippingAddress ?? void 0),
2900
+ ...rate ? { shipping_rate_id: Number(rate) } : {},
2901
+ ...lineIds?.length ? { line_ids: lineIds.map(Number) } : {},
2902
+ ...returnUrl ? { return_url: returnUrl } : {},
2903
+ ...paymentData ? { payment_data: paymentData } : {}
2904
+ };
2905
+ const res = await this.data("/x/b2b/checkout", {
2906
+ method: "POST",
2907
+ auth: true,
2908
+ body,
2909
+ headers: { "Idempotency-Key": attemptKey(`b2b:${JSON.stringify(body)}`) }
2910
+ });
2911
+ return this.placed(res, cart);
2912
+ }
2913
+ // ------------------------------------------------------------ the company's orders
2914
+ /** Admins and approvers see the company's; a buyer sees their own. `q` matches the number, PO or email. */
2915
+ async orders({
2916
+ page = 1,
2917
+ limit = 20,
2918
+ q,
2919
+ overdue
2920
+ } = {}) {
2921
+ return this.engine("/x/b2b/orders", {
2922
+ auth: true,
2923
+ query: { page, limit, q, ...overdue ? { overdue: "true" } : {} }
2924
+ });
2925
+ }
2926
+ // ------------------------------------------------------------ approvals
2927
+ async approvals({
2928
+ page = 1,
2929
+ limit = 20,
2930
+ status
2931
+ } = {}) {
2932
+ return this.engine("/x/b2b/approvals", { auth: true, query: { page, limit, status } });
2933
+ }
2934
+ async approval(approvalId) {
2935
+ return this.data(`/x/b2b/approvals/${id(approvalId)}`, { auth: true });
2936
+ }
2937
+ /**
2938
+ * Admin or approver: places the requested order at the prices it was
2939
+ * requested at. Credit limits and holds still apply. The payment intent is
2940
+ * the approver's to act on — for an account order there is nothing to do.
2941
+ */
2942
+ async approve(approvalId) {
2943
+ const res = await this.data(`/x/b2b/approvals/${id(approvalId)}/approve`, {
2944
+ method: "POST",
2945
+ auth: true
2946
+ });
2947
+ return { approval: res.approval, orderNo: res.order.number, order: toOrder(res.order), payment: res.payment };
2948
+ }
2949
+ async reject(approvalId, reason) {
2950
+ return this.data(`/x/b2b/approvals/${id(approvalId)}/reject`, {
2951
+ method: "POST",
2952
+ auth: true,
2953
+ ...reason ? { body: { reason } } : {}
2954
+ });
2955
+ }
2956
+ /** The buyer who asked withdraws the request. */
2957
+ async cancelApproval(approvalId) {
2958
+ return this.data(`/x/b2b/approvals/${id(approvalId)}/cancel`, { method: "POST", auth: true });
2959
+ }
2960
+ // ------------------------------------------------------------ quotes
2961
+ async quotes({ page = 1, limit = 20, status } = {}) {
2962
+ return this.engine("/x/b2b/quotes", { auth: true, query: { page, limit, status } });
2963
+ }
2964
+ async quote(quoteId) {
2965
+ return this.data(`/x/b2b/quotes/${id(quoteId)}`, { auth: true });
2966
+ }
2967
+ /** Asks the store to price these lines. The store answers with unit prices and an expiry. */
2968
+ async requestQuote({ lines, note }) {
2969
+ return this.data("/x/b2b/quotes", {
2970
+ method: "POST",
2971
+ auth: true,
2972
+ body: {
2973
+ lines: (lines ?? []).map((l) => ({ variant_id: Number(l.variantId), quantity: Number(l.quantity) })),
2974
+ ...note ? { note } : {}
2975
+ }
2976
+ });
2977
+ }
2978
+ /** Places the quote at its agreed prices — or, for a buyer over the threshold, asks for approval. */
2979
+ async acceptQuote(quoteId, opts = {}) {
2980
+ const res = await this.data(`/x/b2b/quotes/${id(quoteId)}/accept`, {
2981
+ method: "POST",
2982
+ auth: true,
2983
+ body: this.placeBody(opts)
2984
+ });
2985
+ return this.placed(res);
2986
+ }
2987
+ async declineQuote(quoteId) {
2988
+ return this.data(`/x/b2b/quotes/${id(quoteId)}/decline`, { method: "POST", auth: true });
2989
+ }
2990
+ // ------------------------------------------------------------ dealer leads
2991
+ /**
2992
+ * The public "find a dealer" form: no account needed. The engine routes it to
2993
+ * the dealer whose territory covers the address — or keeps it — and answers
2994
+ * the same either way, so the form never reveals who got it.
2995
+ */
2996
+ async submitLead(lead) {
2997
+ return this.data("/x/b2b/leads", {
2998
+ method: "POST",
2999
+ body: {
3000
+ ...lead.name ? { name: lead.name } : {},
3001
+ ...lead.email ? { email: lead.email } : {},
3002
+ ...lead.phone ? { phone: lead.phone } : {},
3003
+ ...lead.message ? { message: lead.message } : {},
3004
+ ...lead.country ? { country: lead.country } : {},
3005
+ ...lead.state ? { state: lead.state } : {},
3006
+ ...lead.postalCode ? { postal_code: lead.postalCode } : {},
3007
+ ...lead.productId ? { product_id: Number(lead.productId) } : {},
3008
+ ...lead.variantId ? { variant_id: Number(lead.variantId) } : {},
3009
+ ...lead.source ? { source: lead.source } : {}
3010
+ }
3011
+ });
3012
+ }
3013
+ /** A dealer's admins and approvers: the leads routed to their company. */
3014
+ async leads({ page = 1, limit = 20, status } = {}) {
3015
+ return this.engine("/x/b2b/leads", { auth: true, query: { page, limit, status } });
3016
+ }
3017
+ async setLeadStatus(leadId, status) {
3018
+ return this.data(`/x/b2b/leads/${id(leadId)}`, { method: "PATCH", auth: true, body: { status } });
3019
+ }
3020
+ // ------------------------------------------------------------ internals
3021
+ /** The fields checkout and quote acceptance share. Only declared ones: the engine refuses the rest. */
3022
+ placeBody(opts, fallback) {
3023
+ const raw = opts.address ?? fallback;
3024
+ if (!raw) throw new GoCommerceError("Add a delivery address first.", 400);
3025
+ const address = "line1" in raw ? raw : toEngineAddress(raw);
3026
+ return {
3027
+ address,
3028
+ ...opts.paymentMethod ? { payment_method: String(opts.paymentMethod).toLowerCase() } : {},
3029
+ ...opts.poNumber ? { po_number: String(opts.poNumber).trim() } : {},
3030
+ ...opts.name ?? address.name ? { name: opts.name ?? address.name } : {},
3031
+ ...opts.phone ?? address.phone ? { phone: opts.phone ?? address.phone } : {}
3032
+ };
3033
+ }
3034
+ placed(res, cartId) {
3035
+ if (res.approval && !res.order) return { kind: "approval", approval: res.approval };
3036
+ const order = res.order;
3037
+ if (order.access_token && cartId) {
3038
+ rememberOrder({ number: order.number, token: order.access_token, cartId, reference: res.payment?.reference });
3039
+ }
3040
+ return { kind: "order", orderNo: order.number, order: toOrder(order), payment: res.payment };
3041
+ }
3042
+ };
3043
+ var id = (v) => encodeURIComponent(String(v));
3044
+ var keys = /* @__PURE__ */ new Map();
3045
+ function attemptKey(request) {
3046
+ let key = keys.get(request);
3047
+ if (!key) {
3048
+ const c = globalThis.crypto;
3049
+ key = c?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
3050
+ keys.set(request, key);
3051
+ }
3052
+ return key;
3053
+ }
3054
+ var b2bService = new B2bService();
3055
+
3056
+ export { AddressService, AuthService, AutocompleteService, B2bService, BannerService, BaseService, BlogService, CartService, CatalogService, CategoryService, ChatService, CheckoutService, CollectionService, ContactService, CountryService, CouponService, CurrencyService, DealService, DemoRequestService, EnquiryService, FaqService, FeedbackService, GalleryService, HomeService, InitService, MeilisearchService, MenuService, NewsletterService, OrderService, PageService, PaymentMethodService, PluginService, PluginService as PluginsService, PopularSearchService, PopularityService, ProductService, ProfileService, REDIRECT_GATEWAYS, ReelsService, RegionService, ReviewService, SearchService, SettingService, SettingService as SettingsService, StateService, StoreService, UploadService, UserService, VariantService, VarniCustomDesignService, VarniCustomProductService, VendorService, WarrantyService, WishlistService, addressService, authService, autocompleteService, b2bService, bannerService, blogService, cartService, categoryService, chatService, checkoutService, collectionService, contactService, countryService, couponService, currencyService, dealService, demoRequestService, emptyPage, enquiryService, faqService, feedbackService, galleryService, homeService, initService, meilisearchService, menuService, newsletterService, orderService, pageService, paymentMethodService, pluginService, pluginsService, popularSearchService, popularityService, productService, profileService, qs, reelsService, regionService, reviewService, searchService, settingService, settingsService, stateService, staticStore, storeService, toPage, uploadService, userService, variantService, varniCustomDesignService, varniCustomProductService, vendorService, warrantyService, wishlistService };
2568
3057
  //# sourceMappingURL=index.js.map
2569
3058
  //# sourceMappingURL=index.js.map