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