hookwright 1.8.0 → 1.9.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.
Files changed (2) hide show
  1. package/dist/cli.js +48 -25
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ var init_package = __esm({
24
24
  "package.json"() {
25
25
  package_default = {
26
26
  name: "hookwright",
27
- version: "1.8.0",
27
+ version: "1.9.0",
28
28
  description: "Build real, signed e-commerce webhooks from a live Shopify catalogue \u2014 interactive terminal UI, no backend",
29
29
  keywords: [
30
30
  "webhook",
@@ -1156,7 +1156,13 @@ function selectionFor2(eventName) {
1156
1156
  }
1157
1157
  var COMMON_FIELDS2 = [
1158
1158
  { source: "event_name / eventName", target: "\xABgate\xBB", required: true, note: "the schema matches EITHER spelling" },
1159
- { source: "customer.phone / user_data.phone", target: "phone", required: true, note: "missing phone is rejected on every event" }
1159
+ { source: "customer.phone / user_data.phone", target: "customer.phone", required: true, note: "missing phone is rejected on every event" },
1160
+ { source: "customer.first_name / user_data.firstName", target: "customer.firstName" },
1161
+ { source: "customer.last_name / user_data.lastName", target: "customer.lastName" },
1162
+ { source: "\u2014 (first + last)", target: "customer.fullName", note: "built by the consumer, not sent" },
1163
+ { source: "customer.email / user_data.email", target: "customer.email" },
1164
+ { source: "channel", target: "channel" },
1165
+ { source: "timestamp / created_at", target: "eventTime" }
1160
1166
  ];
1161
1167
  var BROWSING_FIELDS = [
1162
1168
  { source: "uiData.pageUrl / data.page_url", target: "page.url" },
@@ -1170,7 +1176,8 @@ var CART_FIELDS = [
1170
1176
  { source: "line_items[].title", target: "cart.productName", required: true, note: "the first PAID line wins; freebies rank last" },
1171
1177
  { source: "line_items[].product_image", target: "cart.image" },
1172
1178
  { source: "line_items[].price", target: "\u2014 (ranking only)", note: "a zero price marks the line a freebie" },
1173
- { source: "line_items[].quantity", target: "cart.quantity" },
1179
+ { source: "line_items[].quantity", target: "cart.quantity", note: "summed across the cart" },
1180
+ { source: "line_items[]", target: "cart.itemCount", note: "the line count, not the quantity" },
1174
1181
  { source: "total_price", target: "pricing.totalPrice" },
1175
1182
  { source: "subtotal_price", target: "pricing.subtotal" },
1176
1183
  { source: "total_discount", target: "pricing.totalDiscount" },
@@ -1186,7 +1193,9 @@ var FIELD_MAPS2 = {
1186
1193
  { source: "data.product_name", target: "product.name" },
1187
1194
  { source: "data.product_image", target: "product.image" },
1188
1195
  { source: "data.product_url", target: "product.url" },
1189
- { source: "data.product_price", target: "product.price" }
1196
+ { source: "data.product_price", target: "product.price" },
1197
+ { source: "data.product_compare_at_price", target: "product.compareAtPrice" },
1198
+ { source: "data.product_type", target: "product.type" }
1190
1199
  ],
1191
1200
  added_to_cart_ui: [...COMMON_FIELDS2, ...CART_FIELDS],
1192
1201
  checkout_clicked: [...COMMON_FIELDS2, ...CART_FIELDS],
@@ -1197,6 +1206,8 @@ var FIELD_MAPS2 = {
1197
1206
  { source: "order_id", target: "order.id" },
1198
1207
  { source: "order_name", target: "order.name" },
1199
1208
  { source: "payment_mode", target: "order.paymentMode" },
1209
+ { source: "total_shipping", target: "order.totalShipping" },
1210
+ { source: "total_tax", target: "order.totalTax" },
1200
1211
  { source: "total_payable", target: "order.totalPayable" }
1201
1212
  ]
1202
1213
  };
@@ -1244,7 +1255,7 @@ function editableFieldsFor2(eventName) {
1244
1255
  }
1245
1256
  var editableFields4 = editableFieldsFor2(DEFAULT_EVENT2);
1246
1257
  function buildPayload4(ctx) {
1247
- const { config, items, phone, collection } = ctx;
1258
+ const { config, items, phone, collection, live } = ctx;
1248
1259
  const eventName = ctx.eventName ?? DEFAULT_EVENT2;
1249
1260
  const def = eventDef(eventName);
1250
1261
  const now = ctx.now ?? /* @__PURE__ */ new Date();
@@ -1263,7 +1274,7 @@ function buildPayload4(ctx) {
1263
1274
  pageUrl: `https://${domain}/`,
1264
1275
  pageTitle: config.shopify.shop?.name ?? domain,
1265
1276
  landingPageUrl: `https://${domain}/`,
1266
- referrerPageUrl: ""
1277
+ referrerPageUrl: ctx.referrer ?? "https://www.google.com/"
1267
1278
  },
1268
1279
  clientData: { brandUrl: `https://${domain}` }
1269
1280
  },
@@ -1280,7 +1291,7 @@ function buildPayload4(ctx) {
1280
1291
  page_url: isProduct ? `https://${domain}/products/${first?.product?.handle ?? ""}` : `https://${domain}/collections/${collection?.handle ?? "all"}`,
1281
1292
  page_title: isProduct ? first?.product?.title : collection?.title,
1282
1293
  landing_page_url: `https://${domain}/`,
1283
- referrer_page_url: "",
1294
+ referrer_page_url: ctx.referrer ?? "https://www.google.com/",
1284
1295
  ...isProduct ? {
1285
1296
  product_name: first?.product?.title,
1286
1297
  product_image: imageForVariant(first?.product, first?.variant),
@@ -1305,7 +1316,7 @@ function buildPayload4(ctx) {
1305
1316
  const subtotal = money2(lineItems.reduce((sum, li) => sum + Number(li.price) * li.quantity, 0));
1306
1317
  const discount = money2(Math.min(ctx.discount ?? 0, subtotal));
1307
1318
  const total = money2(subtotal - discount);
1308
- const checkoutUrl = cartPermalink(domain, (items ?? []).map(({ variant, quantity }) => ({ variantId: variant?.id, quantity })));
1319
+ const checkoutUrl = live?.kind === "checkout" && live.url ? live.url : cartPermalink(domain, (items ?? []).map(({ variant, quantity }) => ({ variantId: variant?.id, quantity })));
1309
1320
  return {
1310
1321
  event_name: eventName,
1311
1322
  session_id: ctx.sessionId ?? `sf-${now.getTime()}`,
@@ -1327,12 +1338,13 @@ function buildPayload4(ctx) {
1327
1338
  total_discount: discount,
1328
1339
  total_price: total,
1329
1340
  ...eventName === "order_completed" ? {
1330
- order_id: ctx.orderId ?? Math.floor(now.getTime() / 1e3),
1331
- order_name: ctx.orderName ?? `#${String(now.getTime()).slice(-5)}`,
1341
+ // a real order id is what the receiver would look up
1342
+ order_id: live?.kind === "order" ? live.id : ctx.orderId ?? Math.floor(now.getTime() / 1e3),
1343
+ order_name: live?.kind === "order" ? live.name : ctx.orderName ?? `#${String(now.getTime()).slice(-5)}`,
1332
1344
  payment_mode: "prepaid",
1333
1345
  total_shipping: 0,
1334
1346
  total_tax: 0,
1335
- total_payable: total
1347
+ total_payable: live?.kind === "order" ? Number(live.price) : total
1336
1348
  } : {},
1337
1349
  timestamp: now.getTime()
1338
1350
  };
@@ -1482,12 +1494,13 @@ function nationalNumber(raw, regionCode) {
1482
1494
  // src/providers/flexype.mjs
1483
1495
  var GATE_PATH3 = "event_type";
1484
1496
  var GATE_VALUE2 = "ABANDONED_CHECKOUT";
1497
+ var DEFAULT_EVENT3 = GATE_VALUE2;
1485
1498
  var fieldMap5 = [
1486
1499
  { source: "event_type", target: "\xABgate\xBB", required: true, note: "must be ABANDONED_CHECKOUT or the event is dropped" },
1487
1500
  { source: "payload.user.phone_with_dial_code", target: "phone", required: true, note: "falls back to dial_code + phone" },
1488
1501
  { source: "payload.user.first_name", target: "customer.firstName" },
1489
1502
  { source: "payload.user.last_name", target: "customer.lastName" },
1490
- { source: "payload.user.full_name", target: "customer.name" },
1503
+ { source: "payload.user.full_name", target: "customer.fullName", note: "falls back to first + last name" },
1491
1504
  { source: "payload.user.email", target: "customer.email" },
1492
1505
  { source: "payload.checkout_url", target: "checkoutUrl", required: true },
1493
1506
  { source: "payload.session_id", target: "sessionId" },
@@ -1496,7 +1509,8 @@ var fieldMap5 = [
1496
1509
  { source: "payload.cart_items[].image", target: "cart.image" },
1497
1510
  { source: "payload.cart_items[].total_price", target: "pricing.totalPrice", note: "summed across the cart" },
1498
1511
  { source: "payload.cart_items[].total_discount", target: "pricing.totalDiscount", note: "summed across the cart" },
1499
- { source: "payload.cart_items[].quantity", target: "cart.quantity" },
1512
+ { source: "payload.cart_items[].quantity", target: "cart.quantity", note: "summed across the cart" },
1513
+ { source: "payload.cart_items[]", target: "cart.itemCount", note: "the line count, not the quantity" },
1500
1514
  { source: "payload.coupons[0].code", target: "pricing.couponCode" },
1501
1515
  { source: "payload.checkout_context.presentment_currency", target: "pricing.currency" },
1502
1516
  { source: "payload.checkout_context.presentment_country", target: "phone region" },
@@ -1504,6 +1518,9 @@ var fieldMap5 = [
1504
1518
  { source: "payload.metadata.landing_page", target: "attribution.landingPage" },
1505
1519
  { source: "store.shopify_domain", target: "storeUrl" },
1506
1520
  { source: "store.name", target: "store.name" },
1521
+ { source: "store.main_domain", target: "store.mainDomain" },
1522
+ { source: "payload.checkout_context.store_country", target: "store.country" },
1523
+ { source: "payload.metadata.orig_referer", target: "attribution.referrer" },
1507
1524
  { source: "event_id", target: "eventId" },
1508
1525
  { source: "event_time", target: "eventTime" }
1509
1526
  ];
@@ -1524,7 +1541,7 @@ var editableFields5 = [
1524
1541
  { group: "Attribution", path: "payload.metadata.landing_page", label: "Landing page", optional: true }
1525
1542
  ];
1526
1543
  function buildPayload5(ctx) {
1527
- const { config, items, phone } = ctx;
1544
+ const { config, items, phone, live } = ctx;
1528
1545
  const now = ctx.now ?? /* @__PURE__ */ new Date();
1529
1546
  const currency = ctx.currency || config.defaults.currency;
1530
1547
  const domain = config.shopify.domain;
@@ -1544,7 +1561,7 @@ function buildPayload5(ctx) {
1544
1561
  sku: variant?.sku || null
1545
1562
  };
1546
1563
  });
1547
- const checkoutUrl = cartPermalink(domain, (items ?? []).map(({ variant, quantity }) => ({ variantId: variant?.id, quantity })));
1564
+ const checkoutUrl = live?.kind === "checkout" && live.url ? live.url : cartPermalink(domain, (items ?? []).map(({ variant, quantity }) => ({ variantId: variant?.id, quantity })));
1548
1565
  const utm = Object.entries(config.defaults.utm ?? {}).map(([name, value]) => ({ name, value }));
1549
1566
  return {
1550
1567
  event_id: `fx-${now.getTime()}`,
@@ -1579,8 +1596,8 @@ function buildPayload5(ctx) {
1579
1596
  coupons: ctx.couponCode ? [{ code: ctx.couponCode }] : [],
1580
1597
  metadata: {
1581
1598
  landing_page: `https://${domain}/products/${items?.[0]?.product?.handle ?? ""}`,
1582
- orig_referer: "",
1583
- referer_host: "",
1599
+ orig_referer: ctx.referrer ?? "https://www.google.com/",
1600
+ referer_host: "www.google.com",
1584
1601
  utm
1585
1602
  }
1586
1603
  }
@@ -1621,6 +1638,7 @@ var flexype_default = {
1621
1638
  urlKey: "flexype",
1622
1639
  label: "FlexyPe",
1623
1640
  eventType: "flexype_abandoned_checkout",
1641
+ defaultEvent: DEFAULT_EVENT3,
1624
1642
  gate: { path: GATE_PATH3, value: GATE_VALUE2 },
1625
1643
  signatureScheme: "static shared secret in the header (no body signature)",
1626
1644
  signatureHeader: "x-flexype-authorization",
@@ -1647,8 +1665,8 @@ var EVENTS3 = ["return", "exchange"].flatMap(
1647
1665
  subject: requestType === "exchange" ? "the returned product and its replacement" : "the returned product"
1648
1666
  }))
1649
1667
  );
1650
- var DEFAULT_EVENT3 = "return_requested";
1651
- var eventDef2 = (eventName) => EVENTS3.find((e) => e.eventName === eventName) ?? EVENTS3.find((e) => e.eventName === DEFAULT_EVENT3);
1668
+ var DEFAULT_EVENT4 = "return_requested";
1669
+ var eventDef2 = (eventName) => EVENTS3.find((e) => e.eventName === eventName) ?? EVENTS3.find((e) => e.eventName === DEFAULT_EVENT4);
1652
1670
  function eventFromPayload3(payload) {
1653
1671
  const request = payload?.request ?? payload ?? {};
1654
1672
  if (!request.request_type || !request.status) return void 0;
@@ -1703,13 +1721,13 @@ function editableFieldsFor3(eventName) {
1703
1721
  { group: "Pricing", path: p("line_items.0.presentment_price.currency"), label: "Currency" }
1704
1722
  ];
1705
1723
  }
1706
- var editableFields6 = editableFieldsFor3(DEFAULT_EVENT3);
1724
+ var editableFields6 = editableFieldsFor3(DEFAULT_EVENT4);
1707
1725
  function statusBlock(reached, at, comment = null) {
1708
1726
  return { status: reached, comment, created_at: reached ? at : null };
1709
1727
  }
1710
1728
  function buildPayload6(ctx) {
1711
1729
  const { config, items, phone } = ctx;
1712
- const eventName = ctx.eventName ?? DEFAULT_EVENT3;
1730
+ const eventName = ctx.eventName ?? DEFAULT_EVENT4;
1713
1731
  const def = eventDef2(eventName);
1714
1732
  const now = ctx.now ?? /* @__PURE__ */ new Date();
1715
1733
  const currency = ctx.currency || config.defaults.currency;
@@ -1913,7 +1931,7 @@ var return_prime_default = {
1913
1931
  urlKey: "return_prime",
1914
1932
  label: "Return Prime",
1915
1933
  events: EVENTS3,
1916
- defaultEvent: DEFAULT_EVENT3,
1934
+ defaultEvent: DEFAULT_EVENT4,
1917
1935
  eventType: "return_prime_return_requested",
1918
1936
  gate: { path: "request.request_type", value: "return" },
1919
1937
  gateFor: (eventName) => ({ path: "request.status", value: eventDef2(eventName).status }),
@@ -3008,7 +3026,11 @@ async function buildPayload8({ cfg, providerId = "cashfree-occ", items = [], dis
3008
3026
 
3009
3027
  // src/shopify/live.mjs
3010
3028
  var LIVE_EVENTS = {
3011
- nitro: /* @__PURE__ */ new Set(["checkout", "orders/create", "orders/updated"])
3029
+ nitro: /* @__PURE__ */ new Set(["checkout", "orders/create", "orders/updated"]),
3030
+ // shopflo's recovery link and order id are looked up the same way
3031
+ shopflo: /* @__PURE__ */ new Set(["checkout_abandoned", "checkout_clicked", "order_completed"]),
3032
+ // flexype sends one event and it is a recovery link
3033
+ flexype: /* @__PURE__ */ new Set(["ABANDONED_CHECKOUT"])
3012
3034
  };
3013
3035
  function needsLive(providerId, eventName) {
3014
3036
  return LIVE_EVENTS[providerId]?.has(eventName) ?? false;
@@ -3090,8 +3112,9 @@ async function liveOrder({ config, items = [], phone, client = clientFor(config)
3090
3112
  async function resolveLive({ config, providerId, eventName, items, phone, client }) {
3091
3113
  if (!needsLive(providerId, eventName)) return null;
3092
3114
  if (!config?.shopify?.domain || !config?.shopify?.accessToken) return null;
3093
- if (eventName === "checkout") return await liveCheckout({ config, items, ...client ? { client } : {} });
3094
- return await liveOrder({ config, items, phone, ...client ? { client } : {} });
3115
+ const wantsOrder = eventName === "orders/create" || eventName === "orders/updated" || eventName === "order_completed";
3116
+ if (wantsOrder) return await liveOrder({ config, items, phone, ...client ? { client } : {} });
3117
+ return await liveCheckout({ config, items, ...client ? { client } : {} });
3095
3118
  }
3096
3119
 
3097
3120
  // src/core/send.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hookwright",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Build real, signed e-commerce webhooks from a live Shopify catalogue — interactive terminal UI, no backend",
5
5
  "keywords": [
6
6
  "webhook",