hookwright 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +165 -122
  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: "2.0.0",
27
+ version: "2.1.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",
@@ -1792,8 +1792,12 @@ var EVENTS4 = ["return", "exchange"].flatMap(
1792
1792
  status,
1793
1793
  type: `return_prime_${requestType}_${status}`,
1794
1794
  label: `${titleCase(requestType)} ${titleCase(status)}`,
1795
- selection: requestType === "exchange" ? "exchange" : "product",
1796
- subject: requestType === "exchange" ? "the returned product and its replacement" : "the returned product"
1795
+ // A return is one or more products sent back, so it is a multi pick, not the
1796
+ // single "which product was viewed" a browsing event asks for. An exchange is
1797
+ // the same list, followed by the replacement chosen for each line.
1798
+ selection: requestType === "exchange" ? "exchange" : "cart",
1799
+ subject: requestType === "exchange" ? "the returned products and their replacements" : "the returned products",
1800
+ pickPrompt: requestType === "exchange" ? "Select the products being returned for exchange" : "Select the products being returned"
1797
1801
  }))
1798
1802
  );
1799
1803
  var DEFAULT_EVENT4 = "return_requested";
@@ -1806,6 +1810,9 @@ function eventFromPayload4(payload) {
1806
1810
  function selectionFor4(eventName) {
1807
1811
  return eventDef3(eventName).selection;
1808
1812
  }
1813
+ function pickPromptFor(eventName) {
1814
+ return eventDef3(eventName).pickPrompt;
1815
+ }
1809
1816
  var COMMON_FIELDS3 = [
1810
1817
  { source: "request.request_type", target: "\xABgate\xBB + requestType", required: true, note: "return or exchange" },
1811
1818
  { source: "request.status", target: "\xABgate\xBB + status", required: true, note: "requested, approved, received, inspected or rejected" },
@@ -1878,124 +1885,129 @@ function buildPayload6(ctx) {
1878
1885
  const currency = ctx.currency || config.defaults.currency;
1879
1886
  const cust = config.customer;
1880
1887
  const at = now.toISOString();
1881
- const first = items?.[0];
1882
- const second = ctx.exchange ?? items?.[1] ?? first;
1883
- const originalPrice = money2(first?.variant?.price ?? 0);
1884
- const exchangePrice = money2(second?.variant?.price ?? 0);
1888
+ const selected = (items ?? []).filter(Boolean);
1889
+ const replacements = ctx.exchangeItems ?? (ctx.exchange ? [ctx.exchange] : []);
1890
+ const replacementFor = (sel, idx) => replacements[idx] ?? ctx.exchange ?? sel;
1885
1891
  const reachedIndex = STATUSES.indexOf(def.status);
1886
1892
  const reached = (name) => STATUSES.indexOf(name) <= reachedIndex && def.status !== "requested";
1887
- const lineItem = {
1888
- id: Number(first?.variant?.id ?? first?.product?.id ?? 0),
1889
- refund: {
1890
- requested_mode: "store_credit",
1891
- actual_mode: def.status === "rejected" ? "store_credit" : null,
1892
- meta: def.status === "rejected" && ctx.couponCode ? { discount_code: ctx.couponCode } : null,
1893
- status: def.status === "rejected" ? "refunded" : "pending",
1894
- refunded_amount: {
1895
- shop_money: { ...def.status === "rejected" ? { amount: originalPrice } : {}, currency_code: currency },
1896
- presentment_money: { ...def.status === "rejected" ? { amount: originalPrice } : {}, currency_code: currency }
1893
+ const makeLineItem = (sel, idx) => {
1894
+ const repl = replacementFor(sel, idx);
1895
+ const originalPrice = money2(sel?.variant?.price ?? 0);
1896
+ const exchangePrice = money2(repl?.variant?.price ?? 0);
1897
+ return {
1898
+ id: Number(sel?.variant?.id ?? sel?.product?.id ?? 0),
1899
+ refund: {
1900
+ requested_mode: "store_credit",
1901
+ actual_mode: def.status === "rejected" ? "store_credit" : null,
1902
+ meta: def.status === "rejected" && ctx.couponCode ? { discount_code: ctx.couponCode } : null,
1903
+ status: def.status === "rejected" ? "refunded" : "pending",
1904
+ refunded_amount: {
1905
+ shop_money: { ...def.status === "rejected" ? { amount: originalPrice } : {}, currency_code: currency },
1906
+ presentment_money: { ...def.status === "rejected" ? { amount: originalPrice } : {}, currency_code: currency }
1907
+ },
1908
+ refunded_at: def.status === "rejected" ? at : null,
1909
+ comment: null
1897
1910
  },
1898
- refunded_at: def.status === "rejected" ? at : null,
1899
- comment: null
1900
- },
1901
- return_fee: {
1902
- price_set: {
1903
- shop_money: { amount: ctx.returnFee ?? 0, currency_code: currency },
1904
- presentment_money: { amount: ctx.returnFee ?? 0, currency_code: currency }
1911
+ return_fee: {
1912
+ price_set: {
1913
+ shop_money: { amount: ctx.returnFee ?? 0, currency_code: currency },
1914
+ presentment_money: { amount: ctx.returnFee ?? 0, currency_code: currency }
1915
+ },
1916
+ rule: "order"
1905
1917
  },
1906
- rule: "order"
1907
- },
1908
- exchange_fee: {
1909
- price_set: {
1910
- shop_money: { amount: null, currency_code: null },
1911
- presentment_money: { amount: null, currency_code: null }
1912
- }
1913
- },
1914
- exchange: { order: null },
1915
- original_product: {
1916
- title: first?.product?.title,
1917
- variant_title: first?.variant?.title ?? null,
1918
- product_id: Number(first?.product?.id ?? 0),
1919
- variant_id: Number(first?.variant?.id ?? 0),
1920
- image: { src: imageForVariant(first?.product, first?.variant) },
1921
- price: originalPrice,
1922
- sku: first?.variant?.sku || "",
1923
- variant_deleted: false,
1924
- product_deleted: false
1925
- },
1926
- ...def.requestType === "exchange" ? {
1927
- exchange_product: {
1928
- product_id: Number(second?.product?.id ?? 0),
1929
- title: second?.product?.title,
1930
- variant_title: second?.variant?.title ?? "Default Title",
1931
- variant_id: Number(second?.variant?.id ?? 0),
1932
- image: { src: imageForVariant(second?.product, second?.variant) },
1933
- price: exchangePrice,
1934
- sku: second?.variant?.sku || "",
1918
+ exchange_fee: {
1919
+ price_set: {
1920
+ shop_money: { amount: null, currency_code: null },
1921
+ presentment_money: { amount: null, currency_code: null }
1922
+ }
1923
+ },
1924
+ exchange: { order: null },
1925
+ original_product: {
1926
+ title: sel?.product?.title,
1927
+ variant_title: sel?.variant?.title ?? null,
1928
+ product_id: Number(sel?.product?.id ?? 0),
1929
+ variant_id: Number(sel?.variant?.id ?? 0),
1930
+ image: { src: imageForVariant(sel?.product, sel?.variant) },
1931
+ price: originalPrice,
1932
+ sku: sel?.variant?.sku || "",
1935
1933
  variant_deleted: false,
1936
1934
  product_deleted: false
1937
- }
1938
- } : {},
1939
- shipping: [
1940
- def.status === "requested" ? { tracking_available: false, labels: [] } : {
1941
- shipping_company: "bluedartV2",
1942
- tracking_available: true,
1943
- awb: String(now.getTime()).slice(-11),
1944
- tracking_updated_at: null,
1945
- labels: [],
1946
- shipment_status: "Requested",
1947
- tracking_url: `https://www.bluedart.com/trackdartresultthirdparty?trackFor=0&trackNo=${String(now.getTime()).slice(-11)}`
1948
- }
1949
- ],
1950
- quantity: first?.quantity ?? 1,
1951
- reason: ctx.reason ?? "Size of Fit Issues",
1952
- shop_price: {
1953
- actual_amount: originalPrice,
1954
- total_tax: 0,
1955
- return_quantity: first?.quantity ?? 1,
1956
- total_discount: 0,
1957
- shipping_amount: 0,
1958
- taxes_included: true,
1959
- currency,
1960
- do_not_carry_forward_discount: true
1961
- },
1962
- presentment_price: {
1963
- actual_amount: originalPrice,
1964
- total_tax: 0,
1965
- return_quantity: first?.quantity ?? 1,
1966
- total_discount: 0,
1967
- shipping_amount: 0,
1968
- taxes_included: true,
1969
- currency,
1970
- do_not_carry_forward_discount: true
1971
- },
1972
- shopify_order_fulfillment_location: null,
1973
- return_location: def.status === "requested" ? null : {
1974
- id: "rp-location-1",
1975
- fullName: config.shopify.shop?.name ?? config.shopify.domain,
1976
- mobileNumber: nationalNumber(phone, cust.countryCode),
1977
- pinCode: cust.zip,
1978
- streetAddress1: cust.address1,
1979
- streetAddress2: cust.address2 ?? "",
1980
- landmark: "",
1981
- city: cust.city,
1982
- state: cust.province,
1983
- stateCode: cust.provinceCode,
1984
- countryCode: cust.countryCode,
1985
- country: cust.country,
1986
- store: config.shopify.domain,
1987
- facilityCode: "",
1988
- isDefault: true,
1989
- latitude: "",
1990
- longitude: "",
1991
- inStoreReturnExchange: false,
1992
- external: false,
1993
- createdAt: at,
1994
- updatedAt: at
1995
- },
1996
- notes: null,
1997
- tags: []
1935
+ },
1936
+ ...def.requestType === "exchange" ? {
1937
+ exchange_product: {
1938
+ product_id: Number(repl?.product?.id ?? 0),
1939
+ title: repl?.product?.title,
1940
+ variant_title: repl?.variant?.title ?? "Default Title",
1941
+ variant_id: Number(repl?.variant?.id ?? 0),
1942
+ image: { src: imageForVariant(repl?.product, repl?.variant) },
1943
+ price: exchangePrice,
1944
+ sku: repl?.variant?.sku || "",
1945
+ variant_deleted: false,
1946
+ product_deleted: false
1947
+ }
1948
+ } : {},
1949
+ shipping: [
1950
+ def.status === "requested" ? { tracking_available: false, labels: [] } : {
1951
+ shipping_company: "bluedartV2",
1952
+ tracking_available: true,
1953
+ awb: String(now.getTime()).slice(-11),
1954
+ tracking_updated_at: null,
1955
+ labels: [],
1956
+ shipment_status: "Requested",
1957
+ tracking_url: `https://www.bluedart.com/trackdartresultthirdparty?trackFor=0&trackNo=${String(now.getTime()).slice(-11)}`
1958
+ }
1959
+ ],
1960
+ quantity: sel?.quantity ?? 1,
1961
+ reason: ctx.reason ?? "Size of Fit Issues",
1962
+ shop_price: {
1963
+ actual_amount: originalPrice,
1964
+ total_tax: 0,
1965
+ return_quantity: sel?.quantity ?? 1,
1966
+ total_discount: 0,
1967
+ shipping_amount: 0,
1968
+ taxes_included: true,
1969
+ currency,
1970
+ do_not_carry_forward_discount: true
1971
+ },
1972
+ presentment_price: {
1973
+ actual_amount: originalPrice,
1974
+ total_tax: 0,
1975
+ return_quantity: sel?.quantity ?? 1,
1976
+ total_discount: 0,
1977
+ shipping_amount: 0,
1978
+ taxes_included: true,
1979
+ currency,
1980
+ do_not_carry_forward_discount: true
1981
+ },
1982
+ shopify_order_fulfillment_location: null,
1983
+ return_location: def.status === "requested" ? null : {
1984
+ id: "rp-location-1",
1985
+ fullName: config.shopify.shop?.name ?? config.shopify.domain,
1986
+ mobileNumber: nationalNumber(phone, cust.countryCode),
1987
+ pinCode: cust.zip,
1988
+ streetAddress1: cust.address1,
1989
+ streetAddress2: cust.address2 ?? "",
1990
+ landmark: "",
1991
+ city: cust.city,
1992
+ state: cust.province,
1993
+ stateCode: cust.provinceCode,
1994
+ countryCode: cust.countryCode,
1995
+ country: cust.country,
1996
+ store: config.shopify.domain,
1997
+ facilityCode: "",
1998
+ isDefault: true,
1999
+ latitude: "",
2000
+ longitude: "",
2001
+ inStoreReturnExchange: false,
2002
+ external: false,
2003
+ createdAt: at,
2004
+ updatedAt: at
2005
+ },
2006
+ notes: null,
2007
+ tags: []
2008
+ };
1998
2009
  };
2010
+ const lineItems = selected.map(makeLineItem);
1999
2011
  const request = {
2000
2012
  id: `rp-${now.getTime()}`,
2001
2013
  request_number: ctx.requestNumber ?? `${def.requestType === "exchange" ? "EXC" : "RET"}${String(now.getTime()).slice(-3)}`,
@@ -2010,7 +2022,7 @@ function buildPayload6(ctx) {
2010
2022
  name: ctx.orderName ?? `#${String(now.getTime()).slice(-5)}`,
2011
2023
  order_manual_payment: false,
2012
2024
  payment_gateways: [],
2013
- fulfillments: [{ id: Number(String(now.getTime()).slice(-10)), line_items: [lineItem.id], delivery_status: null, delivery_date: null }],
2025
+ fulfillments: [{ id: Number(String(now.getTime()).slice(-10)), line_items: lineItems.map((li) => li.id), delivery_status: null, delivery_date: null }],
2014
2026
  created_at: at
2015
2027
  },
2016
2028
  customer: {
@@ -2040,7 +2052,7 @@ function buildPayload6(ctx) {
2040
2052
  archived: statusBlock(false, at),
2041
2053
  unarchived: statusBlock(false, at),
2042
2054
  incentive: null,
2043
- line_items: [lineItem],
2055
+ line_items: lineItems,
2044
2056
  created_at: at
2045
2057
  };
2046
2058
  return ctx.envelope === "root" ? request : { request };
@@ -2087,6 +2099,7 @@ var return_prime_default = {
2087
2099
  editableFields: editableFields6,
2088
2100
  editableFieldsFor: editableFieldsFor3,
2089
2101
  selectionFor: selectionFor4,
2102
+ pickPromptFor,
2090
2103
  eventFromPayload: eventFromPayload4,
2091
2104
  buildPayload: buildPayload6,
2092
2105
  sign: sign6,
@@ -3566,7 +3579,7 @@ function loadPayload(file) {
3566
3579
  }
3567
3580
 
3568
3581
  // src/core/build.mjs
3569
- function draftPayload({ cfg, providerId = "cashfree-occ", items, discount = 0, phone, eventName, collection, envelope, live, delta }) {
3582
+ function draftPayload({ cfg, providerId = "cashfree-occ", items, discount = 0, phone, eventName, collection, envelope, live, delta, exchangeItems }) {
3570
3583
  const provider = getProvider(providerId);
3571
3584
  const event = eventName ?? provider.defaultEvent;
3572
3585
  const resolvedPhone = phone ?? cfg.customer.phone;
@@ -3580,6 +3593,7 @@ function draftPayload({ cfg, providerId = "cashfree-occ", items, discount = 0, p
3580
3593
  envelope,
3581
3594
  live,
3582
3595
  delta,
3596
+ exchangeItems,
3583
3597
  phone: parsed.ok ? parsed.value : String(resolvedPhone ?? "")
3584
3598
  });
3585
3599
  const editable = provider.editableFieldsFor ? provider.editableFieldsFor(event) : provider.editableFields;
@@ -3595,7 +3609,7 @@ function applyEdits(payload, fields, edits) {
3595
3609
  }
3596
3610
  return payload;
3597
3611
  }
3598
- async function buildPayload10({ cfg, providerId = "cashfree-occ", items = [], discount = 0, phone, checkImageUrls = true, payload: prebuilt, eventName, collection, envelope, live, delta }) {
3612
+ async function buildPayload10({ cfg, providerId = "cashfree-occ", items = [], discount = 0, phone, checkImageUrls = true, payload: prebuilt, eventName, collection, envelope, live, delta, exchangeItems }) {
3599
3613
  const provider0 = getProvider(providerId);
3600
3614
  const needsItems = !provider0.selectionFor || ["cart", "product"].includes(provider0.selectionFor(eventName ?? provider0.defaultEvent));
3601
3615
  if (needsItems && !items.length) throw new ConfigError("no products selected");
@@ -3604,7 +3618,7 @@ async function buildPayload10({ cfg, providerId = "cashfree-occ", items = [], di
3604
3618
  const parsed = toE164(resolvedPhone, cfg.customer.countryCode);
3605
3619
  if (!parsed.ok) throw new ConfigError(`phone is unusable: ${parsed.reason}`);
3606
3620
  const event = eventName ?? provider.defaultEvent;
3607
- const payload = prebuilt ?? provider.buildPayload({ config: cfg, items, discount, phone: parsed.value, eventName: event, collection, envelope, live, delta });
3621
+ const payload = prebuilt ?? provider.buildPayload({ config: cfg, items, discount, phone: parsed.value, eventName: event, collection, envelope, live, delta, exchangeItems });
3608
3622
  const body = JSON.stringify(payload);
3609
3623
  const report = await runAll({ payload, body, provider, config: cfg, checkImageUrls, eventName: event });
3610
3624
  const summary = provider.summarize ? provider.summarize(payload) : {};
@@ -3900,6 +3914,7 @@ var STEP = {
3900
3914
  LOADING: "loading",
3901
3915
  COLLECTION: "collection",
3902
3916
  PICK: "pick",
3917
+ REPLACEMENT: "replacement",
3903
3918
  VARIANT: "variant",
3904
3919
  QUANTITY: "quantity",
3905
3920
  DELTA: "delta",
@@ -3927,6 +3942,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
3927
3942
  const [cursor, setCursor] = useState(0);
3928
3943
  const [items, setItems] = useState([]);
3929
3944
  const [pendingVariant, setPendingVariant] = useState(null);
3945
+ const [exchangeItems, setExchangeItems] = useState([]);
3930
3946
  const [delta, setDelta] = useState([]);
3931
3947
  const isDeltaEvent = providerId === "nitro" && (eventName === "addtocart" || eventName === "removefromcart");
3932
3948
  const [draft, setDraft] = useState(null);
@@ -4049,10 +4065,18 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
4049
4065
  alive = false;
4050
4066
  };
4051
4067
  }, [step === STEP.EVENT, eventName]);
4068
+ const onReplacementsPicked = (ids) => {
4069
+ const picked = ids.map((id) => products.find((p) => p.id === id)).filter(Boolean);
4070
+ if (!picked.length) return;
4071
+ const pairs = picked.map((p) => ({ product: p, variant: p.variants[0], quantity: 1 }));
4072
+ setExchangeItems(pairs);
4073
+ startReview(0, collection, items, delta, pairs);
4074
+ };
4052
4075
  const advance = (nextItems, nextCursor) => {
4053
4076
  if (nextCursor >= chosen.length) {
4054
4077
  setItems(nextItems);
4055
4078
  if (isDeltaEvent) setStep(STEP.DELTA);
4079
+ else if (selection === "exchange") setStep(STEP.REPLACEMENT);
4056
4080
  else if (selection === "cart") setStep(STEP.DISCOUNT);
4057
4081
  else startReview(0, collection, nextItems);
4058
4082
  return;
@@ -4109,7 +4133,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
4109
4133
  const nextItems = [...items, { product, variant: pendingVariant, quantity: qty }];
4110
4134
  advance(nextItems, cursor + 1);
4111
4135
  };
4112
- const startReview = async (discount, chosenCollection = collection, chosenItems = items, chosenDelta = delta) => {
4136
+ const startReview = async (discount, chosenCollection = collection, chosenItems = items, chosenDelta = delta, chosenExchange = exchangeItems) => {
4113
4137
  try {
4114
4138
  let live = null;
4115
4139
  if (needsLive(providerId, eventName)) {
@@ -4124,7 +4148,8 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
4124
4148
  eventName,
4125
4149
  collection: chosenCollection,
4126
4150
  live,
4127
- delta: chosenDelta
4151
+ delta: chosenDelta,
4152
+ exchangeItems: chosenExchange
4128
4153
  });
4129
4154
  setDraft({ ...next, discount, live, delta: chosenDelta });
4130
4155
  setStep(STEP.MODE);
@@ -4247,7 +4272,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
4247
4272
  ] }),
4248
4273
  step === STEP.PICK && selection !== "product" && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4249
4274
  /* @__PURE__ */ jsxs8(Text8, { children: [
4250
- `Select the products in the cart `,
4275
+ `${provider.pickPromptFor?.(eventName) ?? "Select the products in the cart"} `,
4251
4276
  /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "(space toggles \xB7 enter confirms)" })
4252
4277
  ] }),
4253
4278
  /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(
@@ -4262,6 +4287,24 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
4262
4287
  }
4263
4288
  ) })
4264
4289
  ] }),
4290
+ step === STEP.REPLACEMENT && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4291
+ /* @__PURE__ */ jsxs8(Text8, { children: [
4292
+ "What is each returned line being exchanged for? ",
4293
+ /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "(space toggles \xB7 enter confirms)" })
4294
+ ] }),
4295
+ /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: `Picked in order, paired with the ${items.length} line${items.length === 1 ? "" : "s"} coming back.` }),
4296
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(
4297
+ MultiSelect,
4298
+ {
4299
+ visibleOptionCount: 10,
4300
+ options: products.map((p) => ({
4301
+ label: `${truncate(p.title, 44).padEnd(45)} ${money(p.variants[0]?.price, currency)}`,
4302
+ value: p.id
4303
+ })),
4304
+ onSubmit: onReplacementsPicked
4305
+ }
4306
+ ) })
4307
+ ] }),
4265
4308
  step === STEP.DELTA && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4266
4309
  /* @__PURE__ */ jsxs8(Text8, { children: [
4267
4310
  eventName === "addtocart" ? "Which of those lines is the shopper adding now? " : "Which of those lines is the shopper removing? ",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hookwright",
3
- "version": "2.0.0",
3
+ "version": "2.1.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",