hookwright 1.2.2 → 1.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.
- package/README.md +2 -0
- package/dist/cli.js +1096 -100
- 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.
|
|
27
|
+
version: "1.3.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",
|
|
@@ -171,7 +171,11 @@ var DEFAULT_CONFIG = {
|
|
|
171
171
|
targets: {
|
|
172
172
|
"cashfree-occ": { webhookUrl: "", webhookSecret: "" },
|
|
173
173
|
"razorpay-magic": { webhookUrl: "", webhookSecret: "" },
|
|
174
|
-
nitro: { webhookUrl: "", webhookSecret: "" }
|
|
174
|
+
nitro: { webhookUrl: "", webhookSecret: "" },
|
|
175
|
+
shopflo: { webhookUrl: "", webhookSecret: "" },
|
|
176
|
+
flexype: { webhookUrl: "", webhookSecret: "" },
|
|
177
|
+
"return-prime": { webhookUrl: "", webhookSecret: "" },
|
|
178
|
+
fastrr: { webhookUrl: "", webhookSecret: "" }
|
|
175
179
|
},
|
|
176
180
|
customer: {
|
|
177
181
|
firstName: "",
|
|
@@ -407,6 +411,9 @@ function cartPermalink(domain, items) {
|
|
|
407
411
|
function money2(value) {
|
|
408
412
|
return Math.round(Number(value) * 100) / 100;
|
|
409
413
|
}
|
|
414
|
+
function minor(value) {
|
|
415
|
+
return Math.round(Number(value) * 100);
|
|
416
|
+
}
|
|
410
417
|
function normalizeCollection(collection) {
|
|
411
418
|
return {
|
|
412
419
|
id: String(collection.id),
|
|
@@ -603,7 +610,7 @@ var fieldMap2 = [
|
|
|
603
610
|
{ source: "email", target: "email" },
|
|
604
611
|
{ source: "phone", target: "phone", required: true, note: "falls back to Shipping_address.Phone" },
|
|
605
612
|
{ source: "currency", target: "pricing.currency", required: true, note: "read from the ROOT, unlike Cashfree" },
|
|
606
|
-
{ source: "line_items_total", target: "pricing.totalPrice", required: true, note: "
|
|
613
|
+
{ source: "line_items_total", target: "pricing.totalPrice", required: true, note: "a STRING in the smallest currency unit \u2014 the consumer parseFloats it and divides by 100" },
|
|
607
614
|
{ source: "platform", target: "\u2014", note: "documented root field" },
|
|
608
615
|
{ source: "utm_parameters", target: "\u2014", note: "documented root field" },
|
|
609
616
|
{ source: "customer.first_name", target: "customer.firstName" },
|
|
@@ -622,14 +629,15 @@ var fieldMap2 = [
|
|
|
622
629
|
{ source: "customer.Shipping_address.Zip", target: "customer.zip" },
|
|
623
630
|
{ source: "line_items[0].name", target: "cart.productName", required: true },
|
|
624
631
|
{ source: "line_items[0].image_url", target: "cart.image", note: "blank if the product has no image" },
|
|
625
|
-
{ source: "line_items[0].quantity", target: "cart.quantity" }
|
|
632
|
+
{ source: "line_items[0].quantity", target: "cart.quantity" },
|
|
633
|
+
{ source: "line_items[].price", target: "\u2014", note: "smallest currency unit, like line_items_total; the consumer reads no item price" }
|
|
626
634
|
];
|
|
627
635
|
var editableFields2 = [
|
|
628
636
|
{ group: "Checkout", path: "abandoned_checkout_url", label: "Abandoned checkout URL", hint: "must be non-empty \u2014 this is the schema gate" },
|
|
629
637
|
{ group: "Checkout", path: "created_at", label: "Created at" },
|
|
630
638
|
{ group: "Checkout", path: "shop_id", label: "Shop domain" },
|
|
631
639
|
{ group: "Checkout", path: "cart_token", label: "Cart token" },
|
|
632
|
-
{ group: "Pricing", path: "line_items_total", label: "Line items total (
|
|
640
|
+
{ group: "Pricing", path: "line_items_total", label: "Line items total (minor units, string)", hint: "paise for INR, cents for USD; the consumer divides by 100" },
|
|
633
641
|
{ group: "Pricing", path: "currency", label: "Currency" },
|
|
634
642
|
{ group: "Customer", path: "customer.first_name", label: "First name" },
|
|
635
643
|
{ group: "Customer", path: "customer.last_name", label: "Last name" },
|
|
@@ -652,7 +660,7 @@ function buildPayload2(ctx) {
|
|
|
652
660
|
const domain = config.shopify.domain;
|
|
653
661
|
const cust = config.customer;
|
|
654
662
|
const lineItems = items.map(({ product, variant, quantity }) => {
|
|
655
|
-
const price =
|
|
663
|
+
const price = minor(variant?.price ?? 0);
|
|
656
664
|
return {
|
|
657
665
|
image_url: imageForVariant(product, variant),
|
|
658
666
|
name: product.title,
|
|
@@ -670,9 +678,9 @@ function buildPayload2(ctx) {
|
|
|
670
678
|
gram: "0"
|
|
671
679
|
};
|
|
672
680
|
});
|
|
673
|
-
const total =
|
|
674
|
-
const discount =
|
|
675
|
-
const net2 =
|
|
681
|
+
const total = lineItems.reduce((sum, li) => sum + li.price * li.quantity, 0);
|
|
682
|
+
const discount = Math.min(minor(ctx.discount ?? 0), total);
|
|
683
|
+
const net2 = total - discount;
|
|
676
684
|
const checkoutUrl = cartPermalink(domain, items.map(({ variant, quantity }) => ({ variantId: variant?.id, quantity })));
|
|
677
685
|
return {
|
|
678
686
|
shop_id: domain,
|
|
@@ -684,9 +692,9 @@ function buildPayload2(ctx) {
|
|
|
684
692
|
phone,
|
|
685
693
|
abandoned_checkout_url: checkoutUrl,
|
|
686
694
|
currency,
|
|
687
|
-
// Documented as a string, in the smallest currency unit
|
|
695
|
+
// Documented as a string, already in the smallest currency unit.
|
|
688
696
|
// The consumer parses it with parseFloat and divides by 100.
|
|
689
|
-
line_items_total: String(
|
|
697
|
+
line_items_total: String(net2),
|
|
690
698
|
line_items: lineItems,
|
|
691
699
|
tax_details: {},
|
|
692
700
|
promotions: [],
|
|
@@ -743,7 +751,7 @@ function summarize2(payload) {
|
|
|
743
751
|
items: items.map((i) => `${i.name} x${i.quantity}`).join(", "),
|
|
744
752
|
total: Number.parseFloat(payload?.line_items_total ?? "0") / 100,
|
|
745
753
|
currency: payload?.currency,
|
|
746
|
-
extra: `${payload?.line_items_total}
|
|
754
|
+
extra: `${payload?.line_items_total} minor units`,
|
|
747
755
|
link: payload?.abandoned_checkout_url
|
|
748
756
|
};
|
|
749
757
|
}
|
|
@@ -1072,6 +1080,981 @@ var nitro_default = {
|
|
|
1072
1080
|
summarize: summarize3
|
|
1073
1081
|
};
|
|
1074
1082
|
|
|
1083
|
+
// src/providers/shopflo.mjs
|
|
1084
|
+
var EVENTS2 = [
|
|
1085
|
+
{ eventName: "store_page_view", type: "shopflo_store_page_view", label: "Store Page View", selection: "none", subject: "a page, no product", envelope: "eventPayload" },
|
|
1086
|
+
{ eventName: "collection_page_viewed", type: "shopflo_collection_page_viewed", label: "Collection Page View", selection: "collection", subject: "one collection", envelope: "data" },
|
|
1087
|
+
{ eventName: "product_page_viewed", type: "shopflo_product_page_viewed", label: "Product Page View", selection: "product", subject: "one product", envelope: "data" },
|
|
1088
|
+
{ eventName: "added_to_cart_ui", type: "shopflo_added_to_cart", label: "Added To Cart", selection: "cart", subject: "cart contents", envelope: "root" },
|
|
1089
|
+
{ eventName: "checkout_clicked", type: "shopflo_checkout_clicked", label: "Checkout Clicked", selection: "cart", subject: "cart contents", envelope: "root" },
|
|
1090
|
+
{ eventName: "checkout_abandoned", type: "shopflo_checkout_abandoned", label: "Abandoned Checkout", selection: "cart", subject: "cart contents", envelope: "root" },
|
|
1091
|
+
{ eventName: "order_completed", type: "shopflo_order_completed", label: "Order Completed", selection: "cart", subject: "ordered items", envelope: "root" }
|
|
1092
|
+
];
|
|
1093
|
+
var DEFAULT_EVENT2 = "checkout_abandoned";
|
|
1094
|
+
var eventDef = (eventName) => EVENTS2.find((e) => e.eventName === eventName) ?? EVENTS2.find((e) => e.eventName === DEFAULT_EVENT2);
|
|
1095
|
+
function eventFromPayload2(payload) {
|
|
1096
|
+
return payload?.event_name ?? payload?.eventName;
|
|
1097
|
+
}
|
|
1098
|
+
function selectionFor2(eventName) {
|
|
1099
|
+
return eventDef(eventName).selection;
|
|
1100
|
+
}
|
|
1101
|
+
var COMMON_FIELDS2 = [
|
|
1102
|
+
{ source: "event_name / eventName", target: "\xABgate\xBB", required: true, note: "the schema matches EITHER spelling" },
|
|
1103
|
+
{ source: "customer.phone / user_data.phone", target: "phone", required: true, note: "missing phone is rejected on every event" }
|
|
1104
|
+
];
|
|
1105
|
+
var BROWSING_FIELDS = [
|
|
1106
|
+
{ source: "uiData.pageUrl / data.page_url", target: "page.url" },
|
|
1107
|
+
{ source: "uiData.pageTitle / data.page_title", target: "page.title" },
|
|
1108
|
+
{ source: "uiData.landingPageUrl", target: "page.landingPage" },
|
|
1109
|
+
{ source: "uiData.referrerPageUrl", target: "page.referrer" },
|
|
1110
|
+
{ source: "clientData.brandUrl", target: "storeUrl" },
|
|
1111
|
+
{ source: "session_id", target: "sessionId" }
|
|
1112
|
+
];
|
|
1113
|
+
var CART_FIELDS = [
|
|
1114
|
+
{ source: "line_items[].title", target: "cart.productName", required: true, note: "the first PAID line wins; freebies rank last" },
|
|
1115
|
+
{ source: "line_items[].product_image", target: "cart.image" },
|
|
1116
|
+
{ source: "line_items[].price", target: "\u2014 (ranking only)", note: "a zero price marks the line a freebie" },
|
|
1117
|
+
{ source: "line_items[].quantity", target: "cart.quantity" },
|
|
1118
|
+
{ source: "total_price", target: "pricing.totalPrice" },
|
|
1119
|
+
{ source: "subtotal_price", target: "pricing.subtotal" },
|
|
1120
|
+
{ source: "total_discount", target: "pricing.totalDiscount" },
|
|
1121
|
+
{ source: "currency", target: "pricing.currency" },
|
|
1122
|
+
{ source: "note_attributes[shopflo_checkout_url]", target: "checkoutUrl", note: "checkout_abandoned puts the link here, not in token_id" }
|
|
1123
|
+
];
|
|
1124
|
+
var FIELD_MAPS2 = {
|
|
1125
|
+
store_page_view: [...COMMON_FIELDS2, ...BROWSING_FIELDS],
|
|
1126
|
+
collection_page_viewed: [...COMMON_FIELDS2, ...BROWSING_FIELDS, { source: "data.collection_page_url", target: "collection.url" }],
|
|
1127
|
+
product_page_viewed: [
|
|
1128
|
+
...COMMON_FIELDS2,
|
|
1129
|
+
...BROWSING_FIELDS,
|
|
1130
|
+
{ source: "data.product_name", target: "product.name" },
|
|
1131
|
+
{ source: "data.product_image", target: "product.image" },
|
|
1132
|
+
{ source: "data.product_url", target: "product.url" },
|
|
1133
|
+
{ source: "data.product_price", target: "product.price" }
|
|
1134
|
+
],
|
|
1135
|
+
added_to_cart_ui: [...COMMON_FIELDS2, ...CART_FIELDS],
|
|
1136
|
+
checkout_clicked: [...COMMON_FIELDS2, ...CART_FIELDS],
|
|
1137
|
+
checkout_abandoned: [...COMMON_FIELDS2, ...CART_FIELDS],
|
|
1138
|
+
order_completed: [
|
|
1139
|
+
...COMMON_FIELDS2,
|
|
1140
|
+
...CART_FIELDS,
|
|
1141
|
+
{ source: "order_id", target: "order.id" },
|
|
1142
|
+
{ source: "order_name", target: "order.name" },
|
|
1143
|
+
{ source: "payment_mode", target: "order.paymentMode" },
|
|
1144
|
+
{ source: "total_payable", target: "order.totalPayable" }
|
|
1145
|
+
]
|
|
1146
|
+
};
|
|
1147
|
+
var fieldMap4 = FIELD_MAPS2[DEFAULT_EVENT2];
|
|
1148
|
+
function editableFieldsFor2(eventName) {
|
|
1149
|
+
const def = eventDef(eventName);
|
|
1150
|
+
const base = [
|
|
1151
|
+
{ group: "Event", path: "session_id", label: "Session id" },
|
|
1152
|
+
{ group: "Customer", path: "customer.first_name", label: "First name" },
|
|
1153
|
+
{ group: "Customer", path: "customer.last_name", label: "Last name" },
|
|
1154
|
+
{ group: "Customer", path: "customer.email", label: "Email" },
|
|
1155
|
+
{ group: "Customer", path: "customer.phone", label: "Phone", hint: "must parse to E.164" }
|
|
1156
|
+
];
|
|
1157
|
+
if (def.envelope === "eventPayload") {
|
|
1158
|
+
return [
|
|
1159
|
+
{ group: "Event", path: "eventPayload.uiData.pageUrl", label: "Page URL" },
|
|
1160
|
+
{ group: "Event", path: "eventPayload.uiData.pageTitle", label: "Page title" },
|
|
1161
|
+
{ group: "Event", path: "eventPayload.clientData.brandUrl", label: "Store URL" },
|
|
1162
|
+
{ group: "Customer", path: "eventPayload.userData.firstName", label: "First name" },
|
|
1163
|
+
{ group: "Customer", path: "eventPayload.userData.email", label: "Email" },
|
|
1164
|
+
{ group: "Customer", path: "eventPayload.userData.phone", label: "Phone", hint: "must parse to E.164" }
|
|
1165
|
+
];
|
|
1166
|
+
}
|
|
1167
|
+
if (def.envelope === "data") {
|
|
1168
|
+
return [
|
|
1169
|
+
{ group: "Event", path: "data.page_url", label: "Page URL" },
|
|
1170
|
+
{ group: "Event", path: "data.page_title", label: "Page title" },
|
|
1171
|
+
{ group: "Customer", path: "data.user_data.firstName", label: "First name" },
|
|
1172
|
+
{ group: "Customer", path: "data.user_data.email", label: "Email" },
|
|
1173
|
+
{ group: "Customer", path: "data.user_data.phone", label: "Phone", hint: "must parse to E.164" }
|
|
1174
|
+
];
|
|
1175
|
+
}
|
|
1176
|
+
return [
|
|
1177
|
+
...base,
|
|
1178
|
+
{ group: "Pricing", path: "total_price", label: "Total price", type: "number" },
|
|
1179
|
+
{ group: "Pricing", path: "subtotal_price", label: "Subtotal", type: "number" },
|
|
1180
|
+
{ group: "Pricing", path: "total_discount", label: "Discount", type: "number" },
|
|
1181
|
+
{ group: "Pricing", path: "currency", label: "Currency" },
|
|
1182
|
+
...def.eventName === "order_completed" ? [
|
|
1183
|
+
{ group: "Order", path: "order_name", label: "Order name" },
|
|
1184
|
+
{ group: "Order", path: "payment_mode", label: "Payment mode" },
|
|
1185
|
+
{ group: "Order", path: "total_payable", label: "Total payable", type: "number" }
|
|
1186
|
+
] : []
|
|
1187
|
+
];
|
|
1188
|
+
}
|
|
1189
|
+
var editableFields4 = editableFieldsFor2(DEFAULT_EVENT2);
|
|
1190
|
+
function buildPayload4(ctx) {
|
|
1191
|
+
const { config, items, phone, collection } = ctx;
|
|
1192
|
+
const eventName = ctx.eventName ?? DEFAULT_EVENT2;
|
|
1193
|
+
const def = eventDef(eventName);
|
|
1194
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
1195
|
+
const currency = ctx.currency || config.defaults.currency;
|
|
1196
|
+
const domain = config.shopify.domain;
|
|
1197
|
+
const cust = config.customer;
|
|
1198
|
+
const first = items?.[0];
|
|
1199
|
+
if (def.envelope === "eventPayload") {
|
|
1200
|
+
return {
|
|
1201
|
+
eventName,
|
|
1202
|
+
session_id: ctx.sessionId ?? `sf-${now.getTime()}`,
|
|
1203
|
+
channel: "web",
|
|
1204
|
+
eventPayload: {
|
|
1205
|
+
userData: { firstName: cust.firstName, lastName: cust.lastName, email: cust.email, phone },
|
|
1206
|
+
uiData: {
|
|
1207
|
+
pageUrl: `https://${domain}/`,
|
|
1208
|
+
pageTitle: config.shopify.shop?.name ?? domain,
|
|
1209
|
+
landingPageUrl: `https://${domain}/`,
|
|
1210
|
+
referrerPageUrl: ""
|
|
1211
|
+
},
|
|
1212
|
+
clientData: { brandUrl: `https://${domain}` }
|
|
1213
|
+
},
|
|
1214
|
+
timestamp: now.getTime()
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
if (def.envelope === "data") {
|
|
1218
|
+
const isProduct = def.selection === "product";
|
|
1219
|
+
return {
|
|
1220
|
+
event_name: eventName,
|
|
1221
|
+
session_id: ctx.sessionId ?? `sf-${now.getTime()}`,
|
|
1222
|
+
channel: "web",
|
|
1223
|
+
data: {
|
|
1224
|
+
page_url: isProduct ? `https://${domain}/products/${first?.product?.handle ?? ""}` : `https://${domain}/collections/${collection?.handle ?? "all"}`,
|
|
1225
|
+
page_title: isProduct ? first?.product?.title : collection?.title,
|
|
1226
|
+
landing_page_url: `https://${domain}/`,
|
|
1227
|
+
referrer_page_url: "",
|
|
1228
|
+
...isProduct ? {
|
|
1229
|
+
product_name: first?.product?.title,
|
|
1230
|
+
product_image: imageForVariant(first?.product, first?.variant),
|
|
1231
|
+
product_url: `https://${domain}/products/${first?.product?.handle ?? ""}`,
|
|
1232
|
+
product_price: money2(first?.variant?.price ?? 0),
|
|
1233
|
+
product_compare_at_price: money2(first?.variant?.compareAtPrice ?? first?.variant?.price ?? 0),
|
|
1234
|
+
product_type: first?.product?.productType ?? ""
|
|
1235
|
+
} : { collection_page_url: `https://${domain}/collections/${collection?.handle ?? "all"}` },
|
|
1236
|
+
user_data: { firstName: cust.firstName, lastName: cust.lastName, email: cust.email, phone }
|
|
1237
|
+
},
|
|
1238
|
+
timestamp: now.getTime()
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const lineItems = (items ?? []).map(({ product, variant, quantity }) => ({
|
|
1242
|
+
id: String(variant?.id ?? product.id),
|
|
1243
|
+
product_id: String(product.id),
|
|
1244
|
+
title: product.title,
|
|
1245
|
+
price: money2(variant?.price ?? 0).toFixed(2),
|
|
1246
|
+
quantity,
|
|
1247
|
+
product_image: imageForVariant(product, variant)
|
|
1248
|
+
}));
|
|
1249
|
+
const subtotal = money2(lineItems.reduce((sum, li) => sum + Number(li.price) * li.quantity, 0));
|
|
1250
|
+
const discount = money2(Math.min(ctx.discount ?? 0, subtotal));
|
|
1251
|
+
const total = money2(subtotal - discount);
|
|
1252
|
+
const checkoutUrl = cartPermalink(domain, (items ?? []).map(({ variant, quantity }) => ({ variantId: variant?.id, quantity })));
|
|
1253
|
+
return {
|
|
1254
|
+
event_name: eventName,
|
|
1255
|
+
session_id: ctx.sessionId ?? `sf-${now.getTime()}`,
|
|
1256
|
+
token_id: checkoutUrl,
|
|
1257
|
+
source: "shopflo",
|
|
1258
|
+
channel: "web",
|
|
1259
|
+
email: cust.email,
|
|
1260
|
+
phone,
|
|
1261
|
+
created_at: now.toISOString(),
|
|
1262
|
+
// checkout_abandoned carries the recovery link here; the consumer prefers it
|
|
1263
|
+
note_attributes: [
|
|
1264
|
+
{ name: "landing_page", value: `/products/${items?.[0]?.product?.handle ?? ""}` },
|
|
1265
|
+
{ name: "shopflo_checkout_url", value: checkoutUrl }
|
|
1266
|
+
],
|
|
1267
|
+
line_items: lineItems,
|
|
1268
|
+
customer: { uid: "sf-user", first_name: cust.firstName, last_name: cust.lastName, email: cust.email, phone },
|
|
1269
|
+
currency,
|
|
1270
|
+
subtotal_price: subtotal,
|
|
1271
|
+
total_discount: discount,
|
|
1272
|
+
total_price: total,
|
|
1273
|
+
...eventName === "order_completed" ? {
|
|
1274
|
+
order_id: ctx.orderId ?? Math.floor(now.getTime() / 1e3),
|
|
1275
|
+
order_name: ctx.orderName ?? `#${String(now.getTime()).slice(-5)}`,
|
|
1276
|
+
payment_mode: "prepaid",
|
|
1277
|
+
total_shipping: 0,
|
|
1278
|
+
total_tax: 0,
|
|
1279
|
+
total_payable: total
|
|
1280
|
+
} : {},
|
|
1281
|
+
timestamp: now.getTime()
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
function sign4() {
|
|
1285
|
+
return { signature: null, timestamp: null, headers: { "content-type": "application/json" } };
|
|
1286
|
+
}
|
|
1287
|
+
function webhookUrl4(config) {
|
|
1288
|
+
return String(targetFor(config, "shopflo").webhookUrl ?? "").trim();
|
|
1289
|
+
}
|
|
1290
|
+
function webhookSecret4(config) {
|
|
1291
|
+
return String(targetFor(config, "shopflo").webhookSecret ?? "");
|
|
1292
|
+
}
|
|
1293
|
+
function storeOf(payload) {
|
|
1294
|
+
const brandUrl = payload?.eventPayload?.clientData?.brandUrl;
|
|
1295
|
+
const candidate = brandUrl ?? payload?.token_id;
|
|
1296
|
+
if (!candidate) return void 0;
|
|
1297
|
+
try {
|
|
1298
|
+
return new URL(candidate).host;
|
|
1299
|
+
} catch {
|
|
1300
|
+
return candidate;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function summarize4(payload) {
|
|
1304
|
+
const name = eventFromPayload2(payload);
|
|
1305
|
+
const items = Array.isArray(payload?.line_items) ? payload.line_items : [];
|
|
1306
|
+
const envelope = payload?.eventPayload ?? payload?.data ?? {};
|
|
1307
|
+
const user = envelope?.userData ?? envelope?.user_data ?? payload?.customer ?? {};
|
|
1308
|
+
return {
|
|
1309
|
+
event: eventDef(name).label.toLowerCase(),
|
|
1310
|
+
store: storeOf(payload),
|
|
1311
|
+
customer: `${user.firstName ?? user.first_name ?? ""} ${user.lastName ?? user.last_name ?? ""} \xB7 ${payload?.phone ?? user.phone ?? ""}`.replace(/\s+/g, " ").trim(),
|
|
1312
|
+
items: items.map((i) => `${i.title} x${i.quantity}`).join(", ") || (envelope?.product_name ?? "\u2014"),
|
|
1313
|
+
total: payload?.total_price,
|
|
1314
|
+
currency: payload?.currency,
|
|
1315
|
+
extra: `envelope: ${eventDef(name).envelope}`,
|
|
1316
|
+
link: payload?.note_attributes?.find((n) => n.name === "shopflo_checkout_url")?.value ?? payload?.token_id
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
var shopflo_default = {
|
|
1320
|
+
id: "shopflo",
|
|
1321
|
+
urlKey: "shopflo",
|
|
1322
|
+
label: "Shopflo",
|
|
1323
|
+
events: EVENTS2,
|
|
1324
|
+
defaultEvent: DEFAULT_EVENT2,
|
|
1325
|
+
eventType: "shopflo_checkout_abandoned",
|
|
1326
|
+
gate: { path: "event_name", value: DEFAULT_EVENT2 },
|
|
1327
|
+
gateFor: (eventName) => ({ path: eventDef(eventName).envelope === "eventPayload" ? "eventName" : "event_name", value: eventName }),
|
|
1328
|
+
signatureScheme: "none documented \u2014 every request is accepted",
|
|
1329
|
+
signatureHeader: null,
|
|
1330
|
+
fieldMap: fieldMap4,
|
|
1331
|
+
fieldMapFor: (eventName) => FIELD_MAPS2[eventName] ?? FIELD_MAPS2[DEFAULT_EVENT2],
|
|
1332
|
+
editableFields: editableFields4,
|
|
1333
|
+
editableFieldsFor: editableFieldsFor2,
|
|
1334
|
+
selectionFor: selectionFor2,
|
|
1335
|
+
eventFromPayload: eventFromPayload2,
|
|
1336
|
+
buildPayload: buildPayload4,
|
|
1337
|
+
sign: sign4,
|
|
1338
|
+
webhookUrl: webhookUrl4,
|
|
1339
|
+
webhookSecret: webhookSecret4,
|
|
1340
|
+
summarize: summarize4
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
// src/phone.mjs
|
|
1344
|
+
var CALLING_CODES = {
|
|
1345
|
+
IN: "91",
|
|
1346
|
+
US: "1",
|
|
1347
|
+
CA: "1",
|
|
1348
|
+
GB: "44",
|
|
1349
|
+
AE: "971",
|
|
1350
|
+
SG: "65",
|
|
1351
|
+
AU: "61",
|
|
1352
|
+
NZ: "64",
|
|
1353
|
+
DE: "49",
|
|
1354
|
+
FR: "33",
|
|
1355
|
+
IT: "39",
|
|
1356
|
+
ES: "34",
|
|
1357
|
+
NL: "31",
|
|
1358
|
+
SE: "46",
|
|
1359
|
+
RO: "40",
|
|
1360
|
+
PL: "48",
|
|
1361
|
+
ZA: "27",
|
|
1362
|
+
NG: "234",
|
|
1363
|
+
KE: "254",
|
|
1364
|
+
BD: "880",
|
|
1365
|
+
PK: "92",
|
|
1366
|
+
LK: "94",
|
|
1367
|
+
NP: "977",
|
|
1368
|
+
MY: "60",
|
|
1369
|
+
ID: "62",
|
|
1370
|
+
PH: "63",
|
|
1371
|
+
TH: "66",
|
|
1372
|
+
VN: "84",
|
|
1373
|
+
JP: "81",
|
|
1374
|
+
KR: "82",
|
|
1375
|
+
CN: "86",
|
|
1376
|
+
SA: "966",
|
|
1377
|
+
QA: "974",
|
|
1378
|
+
KW: "965",
|
|
1379
|
+
OM: "968",
|
|
1380
|
+
BH: "973",
|
|
1381
|
+
BR: "55",
|
|
1382
|
+
MX: "52"
|
|
1383
|
+
};
|
|
1384
|
+
function callingCode(regionCode) {
|
|
1385
|
+
return CALLING_CODES[String(regionCode || "").toUpperCase()] || null;
|
|
1386
|
+
}
|
|
1387
|
+
function toE164(raw, regionCode) {
|
|
1388
|
+
const cleaned = String(raw ?? "").replace(/\s+/g, "");
|
|
1389
|
+
if (!cleaned) return { ok: false, reason: "phone is empty" };
|
|
1390
|
+
if (cleaned.startsWith("+")) {
|
|
1391
|
+
const digits2 = cleaned.slice(1).replace(/\D/g, "");
|
|
1392
|
+
if (digits2.length < 8 || digits2.length > 15) return { ok: false, reason: `+${digits2} is not a plausible E.164 length` };
|
|
1393
|
+
return { ok: true, value: `+${digits2}` };
|
|
1394
|
+
}
|
|
1395
|
+
const cc = callingCode(regionCode);
|
|
1396
|
+
if (!cc) {
|
|
1397
|
+
return {
|
|
1398
|
+
ok: false,
|
|
1399
|
+
reason: `no country calling code known for region "${regionCode}" \u2014 give the phone in +E.164 form instead`
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
let national = cleaned.replace(/\D/g, "").replace(/^0+/, "");
|
|
1403
|
+
if (national.startsWith(cc) && national.length > 10) national = national.slice(cc.length);
|
|
1404
|
+
const value = `+${cc}${national}`;
|
|
1405
|
+
const digits = value.slice(1);
|
|
1406
|
+
if (digits.length < 8 || digits.length > 15) return { ok: false, reason: `${value} is not a plausible E.164 length` };
|
|
1407
|
+
return { ok: true, value };
|
|
1408
|
+
}
|
|
1409
|
+
var knownRegions = Object.keys(CALLING_CODES);
|
|
1410
|
+
function splitPhone(raw, regionCode) {
|
|
1411
|
+
const digits = String(raw ?? "").replace(/[^\d]/g, "");
|
|
1412
|
+
if (!digits) return { dialCode: "", national: "" };
|
|
1413
|
+
const fromRegion = callingCode(regionCode);
|
|
1414
|
+
if (fromRegion && digits.startsWith(fromRegion)) {
|
|
1415
|
+
return { dialCode: fromRegion, national: digits.slice(fromRegion.length) };
|
|
1416
|
+
}
|
|
1417
|
+
const longestFirst = [...new Set(Object.values(CALLING_CODES))].sort((a, b) => b.length - a.length);
|
|
1418
|
+
const matched = longestFirst.find((code) => digits.startsWith(code));
|
|
1419
|
+
if (matched) return { dialCode: matched, national: digits.slice(matched.length) };
|
|
1420
|
+
return { dialCode: "", national: digits };
|
|
1421
|
+
}
|
|
1422
|
+
function nationalNumber(raw, regionCode) {
|
|
1423
|
+
return splitPhone(raw, regionCode).national;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/providers/flexype.mjs
|
|
1427
|
+
var GATE_PATH3 = "event_type";
|
|
1428
|
+
var GATE_VALUE2 = "ABANDONED_CHECKOUT";
|
|
1429
|
+
var fieldMap5 = [
|
|
1430
|
+
{ source: "event_type", target: "\xABgate\xBB", required: true, note: "must be ABANDONED_CHECKOUT or the event is dropped" },
|
|
1431
|
+
{ source: "payload.user.phone_with_dial_code", target: "phone", required: true, note: "falls back to dial_code + phone" },
|
|
1432
|
+
{ source: "payload.user.first_name", target: "customer.firstName" },
|
|
1433
|
+
{ source: "payload.user.last_name", target: "customer.lastName" },
|
|
1434
|
+
{ source: "payload.user.full_name", target: "customer.name" },
|
|
1435
|
+
{ source: "payload.user.email", target: "customer.email" },
|
|
1436
|
+
{ source: "payload.checkout_url", target: "checkoutUrl", required: true },
|
|
1437
|
+
{ source: "payload.session_id", target: "sessionId" },
|
|
1438
|
+
{ source: "payload.session_state", target: "sessionState" },
|
|
1439
|
+
{ source: "payload.cart_items[].title", target: "cart.productName", required: true, note: "the first PAID line wins; freebies rank last" },
|
|
1440
|
+
{ source: "payload.cart_items[].image", target: "cart.image" },
|
|
1441
|
+
{ source: "payload.cart_items[].total_price", target: "pricing.totalPrice", note: "summed across the cart" },
|
|
1442
|
+
{ source: "payload.cart_items[].total_discount", target: "pricing.totalDiscount", note: "summed across the cart" },
|
|
1443
|
+
{ source: "payload.cart_items[].quantity", target: "cart.quantity" },
|
|
1444
|
+
{ source: "payload.coupons[0].code", target: "pricing.couponCode" },
|
|
1445
|
+
{ source: "payload.checkout_context.presentment_currency", target: "pricing.currency" },
|
|
1446
|
+
{ source: "payload.checkout_context.presentment_country", target: "phone region" },
|
|
1447
|
+
{ source: "payload.metadata.utm[]", target: "utm.*", note: "a list of {name,value} pairs, not an object" },
|
|
1448
|
+
{ source: "payload.metadata.landing_page", target: "attribution.landingPage" },
|
|
1449
|
+
{ source: "store.shopify_domain", target: "storeUrl" },
|
|
1450
|
+
{ source: "store.name", target: "store.name" },
|
|
1451
|
+
{ source: "event_id", target: "eventId" },
|
|
1452
|
+
{ source: "event_time", target: "eventTime" }
|
|
1453
|
+
];
|
|
1454
|
+
var editableFields5 = [
|
|
1455
|
+
{ group: "Event", path: "event_id", label: "Event id" },
|
|
1456
|
+
{ group: "Event", path: "event_time", label: "Event time" },
|
|
1457
|
+
{ group: "Checkout", path: "payload.session_id", label: "Session id" },
|
|
1458
|
+
{ group: "Checkout", path: "payload.checkout_url", label: "Checkout URL", hint: "the link the shopper is sent back to" },
|
|
1459
|
+
{ group: "Checkout", path: "payload.session_state", label: "Session state" },
|
|
1460
|
+
{ group: "Customer", path: "payload.user.first_name", label: "First name" },
|
|
1461
|
+
{ group: "Customer", path: "payload.user.last_name", label: "Last name" },
|
|
1462
|
+
{ group: "Customer", path: "payload.user.email", label: "Email" },
|
|
1463
|
+
{ group: "Customer", path: "payload.user.phone_with_dial_code", label: "Phone", hint: "already E.164 shaped" },
|
|
1464
|
+
{ group: "Pricing", path: "payload.checkout_context.presentment_currency", label: "Currency" },
|
|
1465
|
+
{ group: "Pricing", path: "payload.coupons.0.code", label: "Coupon code", optional: true },
|
|
1466
|
+
{ group: "Store", path: "store.name", label: "Store name" },
|
|
1467
|
+
{ group: "Store", path: "store.shopify_domain", label: "Shopify domain" },
|
|
1468
|
+
{ group: "Attribution", path: "payload.metadata.landing_page", label: "Landing page", optional: true }
|
|
1469
|
+
];
|
|
1470
|
+
function buildPayload5(ctx) {
|
|
1471
|
+
const { config, items, phone } = ctx;
|
|
1472
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
1473
|
+
const currency = ctx.currency || config.defaults.currency;
|
|
1474
|
+
const domain = config.shopify.domain;
|
|
1475
|
+
const cust = config.customer;
|
|
1476
|
+
const { dialCode, national } = splitPhone(phone, cust.countryCode);
|
|
1477
|
+
const cartItems = (items ?? []).map(({ product, variant, quantity }) => {
|
|
1478
|
+
const unit = money2(variant?.price ?? 0);
|
|
1479
|
+
return {
|
|
1480
|
+
title: product.title,
|
|
1481
|
+
image: imageForVariant(product, variant),
|
|
1482
|
+
price: unit,
|
|
1483
|
+
total_price: money2(unit * quantity),
|
|
1484
|
+
total_discount: 0,
|
|
1485
|
+
quantity,
|
|
1486
|
+
variant_id: String(variant?.id ?? ""),
|
|
1487
|
+
product_id: String(product.id),
|
|
1488
|
+
sku: variant?.sku || null
|
|
1489
|
+
};
|
|
1490
|
+
});
|
|
1491
|
+
const checkoutUrl = cartPermalink(domain, (items ?? []).map(({ variant, quantity }) => ({ variantId: variant?.id, quantity })));
|
|
1492
|
+
const utm = Object.entries(config.defaults.utm ?? {}).map(([name, value]) => ({ name, value }));
|
|
1493
|
+
return {
|
|
1494
|
+
event_id: `fx-${now.getTime()}`,
|
|
1495
|
+
event_type: GATE_VALUE2,
|
|
1496
|
+
event_time: now.toISOString(),
|
|
1497
|
+
store: {
|
|
1498
|
+
id: 1,
|
|
1499
|
+
name: config.shopify.shop?.name ?? domain,
|
|
1500
|
+
shopify_domain: domain,
|
|
1501
|
+
main_domain: `https://${domain}`
|
|
1502
|
+
},
|
|
1503
|
+
payload: {
|
|
1504
|
+
session_id: `fx-sess-${now.getTime()}`,
|
|
1505
|
+
session_state: "ABANDONED",
|
|
1506
|
+
checkout_url: checkoutUrl,
|
|
1507
|
+
user: {
|
|
1508
|
+
first_name: cust.firstName,
|
|
1509
|
+
last_name: cust.lastName,
|
|
1510
|
+
full_name: `${cust.firstName} ${cust.lastName}`.trim(),
|
|
1511
|
+
email: cust.email,
|
|
1512
|
+
phone: national,
|
|
1513
|
+
dial_code: dialCode,
|
|
1514
|
+
phone_with_dial_code: phone
|
|
1515
|
+
},
|
|
1516
|
+
checkout_context: {
|
|
1517
|
+
presentment_currency: currency,
|
|
1518
|
+
store_currency: currency,
|
|
1519
|
+
presentment_country: cust.countryCode,
|
|
1520
|
+
store_country: cust.country
|
|
1521
|
+
},
|
|
1522
|
+
cart_items: cartItems,
|
|
1523
|
+
coupons: ctx.couponCode ? [{ code: ctx.couponCode }] : [],
|
|
1524
|
+
metadata: {
|
|
1525
|
+
landing_page: `https://${domain}/products/${items?.[0]?.product?.handle ?? ""}`,
|
|
1526
|
+
orig_referer: "",
|
|
1527
|
+
referer_host: "",
|
|
1528
|
+
utm
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
function sign5({ secret }) {
|
|
1534
|
+
return {
|
|
1535
|
+
signature: null,
|
|
1536
|
+
timestamp: null,
|
|
1537
|
+
headers: {
|
|
1538
|
+
"content-type": "application/json",
|
|
1539
|
+
"x-flexype-authorization": String(secret ?? "")
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
function webhookUrl5(config) {
|
|
1544
|
+
return String(targetFor(config, "flexype").webhookUrl ?? "").trim();
|
|
1545
|
+
}
|
|
1546
|
+
function webhookSecret5(config) {
|
|
1547
|
+
return String(targetFor(config, "flexype").webhookSecret ?? "");
|
|
1548
|
+
}
|
|
1549
|
+
function summarize5(payload) {
|
|
1550
|
+
const p = payload?.payload ?? {};
|
|
1551
|
+
const items = Array.isArray(p.cart_items) ? p.cart_items : [];
|
|
1552
|
+
return {
|
|
1553
|
+
event: "abandoned checkout",
|
|
1554
|
+
store: payload?.store?.shopify_domain,
|
|
1555
|
+
customer: `${p.user?.full_name ?? ""} \xB7 ${p.user?.phone_with_dial_code ?? ""}`.trim(),
|
|
1556
|
+
items: items.map((i) => `${i.title} x${i.quantity}`).join(", "),
|
|
1557
|
+
total: items.reduce((sum, i) => sum + Number(i.total_price ?? 0), 0),
|
|
1558
|
+
currency: p.checkout_context?.presentment_currency,
|
|
1559
|
+
extra: p.coupons?.[0]?.code ? `coupon ${p.coupons[0].code}` : "no coupon",
|
|
1560
|
+
link: p.checkout_url
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
var flexype_default = {
|
|
1564
|
+
id: "flexype",
|
|
1565
|
+
urlKey: "flexype",
|
|
1566
|
+
label: "FlexyPe",
|
|
1567
|
+
eventType: "flexype_abandoned_checkout",
|
|
1568
|
+
gate: { path: GATE_PATH3, value: GATE_VALUE2 },
|
|
1569
|
+
signatureScheme: "static shared secret in the header (no body signature)",
|
|
1570
|
+
signatureHeader: "x-flexype-authorization",
|
|
1571
|
+
fieldMap: fieldMap5,
|
|
1572
|
+
editableFields: editableFields5,
|
|
1573
|
+
buildPayload: buildPayload5,
|
|
1574
|
+
sign: sign5,
|
|
1575
|
+
webhookUrl: webhookUrl5,
|
|
1576
|
+
webhookSecret: webhookSecret5,
|
|
1577
|
+
summarize: summarize5
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
// src/providers/return-prime.mjs
|
|
1581
|
+
var STATUSES = ["requested", "approved", "received", "inspected", "rejected"];
|
|
1582
|
+
var titleCase = (v) => v.charAt(0).toUpperCase() + v.slice(1);
|
|
1583
|
+
var EVENTS3 = ["return", "exchange"].flatMap(
|
|
1584
|
+
(requestType) => STATUSES.map((status) => ({
|
|
1585
|
+
eventName: `${requestType}_${status}`,
|
|
1586
|
+
requestType,
|
|
1587
|
+
status,
|
|
1588
|
+
type: `return_prime_${requestType}_${status}`,
|
|
1589
|
+
label: `${titleCase(requestType)} ${titleCase(status)}`,
|
|
1590
|
+
selection: requestType === "exchange" ? "exchange" : "product",
|
|
1591
|
+
subject: requestType === "exchange" ? "the returned product and its replacement" : "the returned product"
|
|
1592
|
+
}))
|
|
1593
|
+
);
|
|
1594
|
+
var DEFAULT_EVENT3 = "return_requested";
|
|
1595
|
+
var eventDef2 = (eventName) => EVENTS3.find((e) => e.eventName === eventName) ?? EVENTS3.find((e) => e.eventName === DEFAULT_EVENT3);
|
|
1596
|
+
function eventFromPayload3(payload) {
|
|
1597
|
+
const request = payload?.request ?? payload ?? {};
|
|
1598
|
+
if (!request.request_type || !request.status) return void 0;
|
|
1599
|
+
return `${request.request_type}_${request.status}`;
|
|
1600
|
+
}
|
|
1601
|
+
function selectionFor3(eventName) {
|
|
1602
|
+
return eventDef2(eventName).selection;
|
|
1603
|
+
}
|
|
1604
|
+
var COMMON_FIELDS3 = [
|
|
1605
|
+
{ source: "request_type", target: "\xABgate\xBB + requestType", required: true, note: "return or exchange" },
|
|
1606
|
+
{ source: "status", target: "\xABgate\xBB + status", required: true, note: "requested, approved, received, inspected or rejected" },
|
|
1607
|
+
{ source: "customer.phone", target: "phone", required: true, note: "missing phone is rejected on every event" },
|
|
1608
|
+
{ source: "customer.address.country_code", target: "phone region" },
|
|
1609
|
+
{ source: "request_number", target: "requestNumber" },
|
|
1610
|
+
{ source: "order.name", target: "order.name" },
|
|
1611
|
+
{ source: "line_items[].original_product.title", target: "product.name", required: true, note: "the first PAID line wins; freebies rank last" },
|
|
1612
|
+
{ source: "line_items[].original_product.image.src", target: "product.image" },
|
|
1613
|
+
{ source: "line_items[].original_product.price", target: "product.price", note: "a zero price marks the line a freebie" },
|
|
1614
|
+
{ source: "line_items[].reason", target: "reason" },
|
|
1615
|
+
{ source: "line_items[].presentment_price.actual_amount", target: "pricing.totalPrice" },
|
|
1616
|
+
{ source: "line_items[].return_fee.price_set\u2026amount", target: "pricing.returnFee" },
|
|
1617
|
+
{ source: "line_items[].refund.status", target: "refund.status" },
|
|
1618
|
+
{ source: "line_items[].refund.meta.discount_code", target: "refund.discountCode", note: "only present once store credit is issued" },
|
|
1619
|
+
{ source: "line_items[].shipping[0].awb", target: "shipping.awb" },
|
|
1620
|
+
{ source: "approved/received/inspected/rejected.created_at", target: "timeline.*" }
|
|
1621
|
+
];
|
|
1622
|
+
var EXCHANGE_FIELDS = [
|
|
1623
|
+
{ source: "line_items[].exchange_product.title", target: "exchangeProduct.name", note: "exchanges only \u2014 returns never carry it" },
|
|
1624
|
+
{ source: "line_items[].exchange_product.price", target: "exchangeProduct.price + pricing.priceDifference", note: "difference is signed: positive means the shopper owes more" }
|
|
1625
|
+
];
|
|
1626
|
+
var fieldMap6 = COMMON_FIELDS3;
|
|
1627
|
+
function fieldMapFor(eventName) {
|
|
1628
|
+
return eventDef2(eventName).requestType === "exchange" ? [...COMMON_FIELDS3, ...EXCHANGE_FIELDS] : COMMON_FIELDS3;
|
|
1629
|
+
}
|
|
1630
|
+
function editableFieldsFor3(eventName) {
|
|
1631
|
+
const def = eventDef2(eventName);
|
|
1632
|
+
const p = (path6) => def.envelope === "root" ? path6 : `request.${path6}`;
|
|
1633
|
+
return [
|
|
1634
|
+
{ group: "Request", path: p("request_number"), label: "Request number" },
|
|
1635
|
+
{ group: "Request", path: p("created_at"), label: "Created at" },
|
|
1636
|
+
{ group: "Request", path: p("line_items.0.reason"), label: "Reason" },
|
|
1637
|
+
{ group: "Order", path: p("order.name"), label: "Order name" },
|
|
1638
|
+
{ group: "Customer", path: p("customer.name"), label: "Customer name" },
|
|
1639
|
+
{ group: "Customer", path: p("customer.email"), label: "Email" },
|
|
1640
|
+
{ group: "Customer", path: p("customer.phone"), label: "Phone", hint: "already E.164 shaped" },
|
|
1641
|
+
{ group: "Shipping", path: p("customer.address.address_line_1"), label: "Address line 1" },
|
|
1642
|
+
{ group: "Shipping", path: p("customer.address.city"), label: "City" },
|
|
1643
|
+
{ group: "Shipping", path: p("customer.address.province"), label: "Province / state" },
|
|
1644
|
+
{ group: "Shipping", path: p("customer.address.postal_code"), label: "Postal code" },
|
|
1645
|
+
{ group: "Shipping", path: p("customer.address.country_code"), label: "Country code", hint: "region used to parse the phone" },
|
|
1646
|
+
{ group: "Pricing", path: p("line_items.0.presentment_price.actual_amount"), label: "Item amount", type: "number" },
|
|
1647
|
+
{ group: "Pricing", path: p("line_items.0.presentment_price.currency"), label: "Currency" }
|
|
1648
|
+
];
|
|
1649
|
+
}
|
|
1650
|
+
var editableFields6 = editableFieldsFor3(DEFAULT_EVENT3);
|
|
1651
|
+
function statusBlock(reached, at, comment = null) {
|
|
1652
|
+
return { status: reached, comment, created_at: reached ? at : null };
|
|
1653
|
+
}
|
|
1654
|
+
function buildPayload6(ctx) {
|
|
1655
|
+
const { config, items, phone } = ctx;
|
|
1656
|
+
const eventName = ctx.eventName ?? DEFAULT_EVENT3;
|
|
1657
|
+
const def = eventDef2(eventName);
|
|
1658
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
1659
|
+
const currency = ctx.currency || config.defaults.currency;
|
|
1660
|
+
const cust = config.customer;
|
|
1661
|
+
const at = now.toISOString();
|
|
1662
|
+
const first = items?.[0];
|
|
1663
|
+
const second = items?.[1] ?? first;
|
|
1664
|
+
const originalPrice = money2(first?.variant?.price ?? 0);
|
|
1665
|
+
const exchangePrice = money2(second?.variant?.price ?? 0);
|
|
1666
|
+
const reachedIndex = STATUSES.indexOf(def.status);
|
|
1667
|
+
const reached = (name) => STATUSES.indexOf(name) <= reachedIndex && def.status !== "requested";
|
|
1668
|
+
const lineItem = {
|
|
1669
|
+
id: Number(first?.variant?.id ?? first?.product?.id ?? 0),
|
|
1670
|
+
refund: {
|
|
1671
|
+
requested_mode: "store_credit",
|
|
1672
|
+
actual_mode: def.status === "rejected" ? "store_credit" : null,
|
|
1673
|
+
meta: def.status === "rejected" && ctx.couponCode ? { discount_code: ctx.couponCode } : null,
|
|
1674
|
+
status: def.status === "rejected" ? "refunded" : "pending",
|
|
1675
|
+
refunded_amount: {
|
|
1676
|
+
shop_money: { ...def.status === "rejected" ? { amount: originalPrice } : {}, currency_code: currency },
|
|
1677
|
+
presentment_money: { ...def.status === "rejected" ? { amount: originalPrice } : {}, currency_code: currency }
|
|
1678
|
+
},
|
|
1679
|
+
refunded_at: def.status === "rejected" ? at : null,
|
|
1680
|
+
comment: null
|
|
1681
|
+
},
|
|
1682
|
+
return_fee: {
|
|
1683
|
+
price_set: {
|
|
1684
|
+
shop_money: { amount: ctx.returnFee ?? 0, currency_code: currency },
|
|
1685
|
+
presentment_money: { amount: ctx.returnFee ?? 0, currency_code: currency }
|
|
1686
|
+
},
|
|
1687
|
+
rule: "order"
|
|
1688
|
+
},
|
|
1689
|
+
exchange_fee: {
|
|
1690
|
+
price_set: {
|
|
1691
|
+
shop_money: { amount: null, currency_code: null },
|
|
1692
|
+
presentment_money: { amount: null, currency_code: null }
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
exchange: { order: null },
|
|
1696
|
+
original_product: {
|
|
1697
|
+
title: first?.product?.title,
|
|
1698
|
+
variant_title: first?.variant?.title ?? null,
|
|
1699
|
+
product_id: Number(first?.product?.id ?? 0),
|
|
1700
|
+
variant_id: Number(first?.variant?.id ?? 0),
|
|
1701
|
+
image: { src: imageForVariant(first?.product, first?.variant) },
|
|
1702
|
+
price: originalPrice,
|
|
1703
|
+
sku: first?.variant?.sku || "",
|
|
1704
|
+
variant_deleted: false,
|
|
1705
|
+
product_deleted: false
|
|
1706
|
+
},
|
|
1707
|
+
...def.requestType === "exchange" ? {
|
|
1708
|
+
exchange_product: {
|
|
1709
|
+
product_id: Number(second?.product?.id ?? 0),
|
|
1710
|
+
title: second?.product?.title,
|
|
1711
|
+
variant_title: second?.variant?.title ?? "Default Title",
|
|
1712
|
+
variant_id: Number(second?.variant?.id ?? 0),
|
|
1713
|
+
image: { src: imageForVariant(second?.product, second?.variant) },
|
|
1714
|
+
price: exchangePrice,
|
|
1715
|
+
sku: second?.variant?.sku || "",
|
|
1716
|
+
variant_deleted: false,
|
|
1717
|
+
product_deleted: false
|
|
1718
|
+
}
|
|
1719
|
+
} : {},
|
|
1720
|
+
shipping: [
|
|
1721
|
+
def.status === "requested" ? { tracking_available: false, labels: [] } : {
|
|
1722
|
+
shipping_company: "bluedartV2",
|
|
1723
|
+
tracking_available: true,
|
|
1724
|
+
awb: String(now.getTime()).slice(-11),
|
|
1725
|
+
tracking_updated_at: null,
|
|
1726
|
+
labels: [],
|
|
1727
|
+
shipment_status: "Requested",
|
|
1728
|
+
tracking_url: `https://www.bluedart.com/trackdartresultthirdparty?trackFor=0&trackNo=${String(now.getTime()).slice(-11)}`
|
|
1729
|
+
}
|
|
1730
|
+
],
|
|
1731
|
+
quantity: first?.quantity ?? 1,
|
|
1732
|
+
reason: ctx.reason ?? "Size of Fit Issues",
|
|
1733
|
+
shop_price: {
|
|
1734
|
+
actual_amount: originalPrice,
|
|
1735
|
+
total_tax: 0,
|
|
1736
|
+
return_quantity: first?.quantity ?? 1,
|
|
1737
|
+
total_discount: 0,
|
|
1738
|
+
shipping_amount: 0,
|
|
1739
|
+
taxes_included: true,
|
|
1740
|
+
currency,
|
|
1741
|
+
do_not_carry_forward_discount: true
|
|
1742
|
+
},
|
|
1743
|
+
presentment_price: {
|
|
1744
|
+
actual_amount: originalPrice,
|
|
1745
|
+
total_tax: 0,
|
|
1746
|
+
return_quantity: first?.quantity ?? 1,
|
|
1747
|
+
total_discount: 0,
|
|
1748
|
+
shipping_amount: 0,
|
|
1749
|
+
taxes_included: true,
|
|
1750
|
+
currency,
|
|
1751
|
+
do_not_carry_forward_discount: true
|
|
1752
|
+
},
|
|
1753
|
+
shopify_order_fulfillment_location: null,
|
|
1754
|
+
return_location: def.status === "requested" ? null : {
|
|
1755
|
+
id: "rp-location-1",
|
|
1756
|
+
fullName: config.shopify.shop?.name ?? config.shopify.domain,
|
|
1757
|
+
mobileNumber: nationalNumber(phone, cust.countryCode),
|
|
1758
|
+
pinCode: cust.zip,
|
|
1759
|
+
streetAddress1: cust.address1,
|
|
1760
|
+
streetAddress2: cust.address2 ?? "",
|
|
1761
|
+
landmark: "",
|
|
1762
|
+
city: cust.city,
|
|
1763
|
+
state: cust.province,
|
|
1764
|
+
stateCode: cust.provinceCode,
|
|
1765
|
+
countryCode: cust.countryCode,
|
|
1766
|
+
country: cust.country,
|
|
1767
|
+
store: config.shopify.domain,
|
|
1768
|
+
facilityCode: "",
|
|
1769
|
+
isDefault: true,
|
|
1770
|
+
latitude: "",
|
|
1771
|
+
longitude: "",
|
|
1772
|
+
inStoreReturnExchange: false,
|
|
1773
|
+
external: false,
|
|
1774
|
+
createdAt: at,
|
|
1775
|
+
updatedAt: at
|
|
1776
|
+
},
|
|
1777
|
+
notes: null,
|
|
1778
|
+
tags: []
|
|
1779
|
+
};
|
|
1780
|
+
const request = {
|
|
1781
|
+
id: `rp-${now.getTime()}`,
|
|
1782
|
+
request_number: ctx.requestNumber ?? `${def.requestType === "exchange" ? "EXC" : "RET"}${String(now.getTime()).slice(-3)}`,
|
|
1783
|
+
request_type: def.requestType,
|
|
1784
|
+
status: def.status,
|
|
1785
|
+
manual_request: false,
|
|
1786
|
+
channel: 97426,
|
|
1787
|
+
smart_exchange: false,
|
|
1788
|
+
payment_details: null,
|
|
1789
|
+
order: {
|
|
1790
|
+
id: Number(ctx.orderId ?? String(now.getTime()).slice(-10)),
|
|
1791
|
+
name: ctx.orderName ?? `#${String(now.getTime()).slice(-5)}`,
|
|
1792
|
+
order_manual_payment: false,
|
|
1793
|
+
payment_gateways: [],
|
|
1794
|
+
fulfillments: [{ id: Number(String(now.getTime()).slice(-10)), line_items: [lineItem.id], delivery_status: null, delivery_date: null }],
|
|
1795
|
+
created_at: at
|
|
1796
|
+
},
|
|
1797
|
+
customer: {
|
|
1798
|
+
id: Number(String(now.getTime()).slice(-10)),
|
|
1799
|
+
name: `${cust.firstName} ${cust.lastName}`.trim(),
|
|
1800
|
+
email: cust.email,
|
|
1801
|
+
phone,
|
|
1802
|
+
address: {
|
|
1803
|
+
first_name: cust.firstName,
|
|
1804
|
+
last_name: cust.lastName || null,
|
|
1805
|
+
postal_code: cust.zip,
|
|
1806
|
+
city: cust.city,
|
|
1807
|
+
province: cust.province,
|
|
1808
|
+
country_code: cust.countryCode,
|
|
1809
|
+
province_code: cust.provinceCode,
|
|
1810
|
+
address_line_1: cust.address1,
|
|
1811
|
+
address_line_2: cust.address2 ?? "",
|
|
1812
|
+
country: cust.country,
|
|
1813
|
+
address_line_3: ""
|
|
1814
|
+
},
|
|
1815
|
+
bank: { account_holder_name: "", account_number: "", confirm_account_number: "", ifsc_code: "" }
|
|
1816
|
+
},
|
|
1817
|
+
approved: statusBlock(reached("approved"), at),
|
|
1818
|
+
received: statusBlock(reached("received"), at),
|
|
1819
|
+
inspected: statusBlock(reached("inspected"), at, def.status === "inspected" ? "quickreply test" : null),
|
|
1820
|
+
rejected: statusBlock(def.status === "rejected", at),
|
|
1821
|
+
archived: statusBlock(false, at),
|
|
1822
|
+
unarchived: statusBlock(false, at),
|
|
1823
|
+
incentive: null,
|
|
1824
|
+
line_items: [lineItem],
|
|
1825
|
+
created_at: at
|
|
1826
|
+
};
|
|
1827
|
+
return ctx.envelope === "root" ? request : { request };
|
|
1828
|
+
}
|
|
1829
|
+
function sign6() {
|
|
1830
|
+
return { signature: null, timestamp: null, headers: { "content-type": "application/json" } };
|
|
1831
|
+
}
|
|
1832
|
+
function webhookUrl6(config) {
|
|
1833
|
+
return String(targetFor(config, "return-prime").webhookUrl ?? "").trim();
|
|
1834
|
+
}
|
|
1835
|
+
function webhookSecret6(config) {
|
|
1836
|
+
return String(targetFor(config, "return-prime").webhookSecret ?? "");
|
|
1837
|
+
}
|
|
1838
|
+
function summarize6(payload) {
|
|
1839
|
+
const r = payload?.request ?? payload ?? {};
|
|
1840
|
+
const item = (Array.isArray(r.line_items) ? r.line_items : [])[0] ?? {};
|
|
1841
|
+
const original = item.original_product ?? {};
|
|
1842
|
+
const exchange = item.exchange_product;
|
|
1843
|
+
const difference = exchange ? money2(Number(exchange.price ?? 0) - Number(original.price ?? 0)) : void 0;
|
|
1844
|
+
return {
|
|
1845
|
+
event: `${r.request_type ?? "?"} ${r.status ?? "?"}`,
|
|
1846
|
+
store: item.return_location?.store ?? r.order?.name,
|
|
1847
|
+
customer: `${r.customer?.name ?? ""} \xB7 ${r.customer?.phone ?? ""}`.trim(),
|
|
1848
|
+
items: exchange ? `${original.title} \u2192 ${exchange.title}` : original.title,
|
|
1849
|
+
total: item.presentment_price?.actual_amount,
|
|
1850
|
+
currency: item.presentment_price?.currency,
|
|
1851
|
+
extra: difference === void 0 ? `${r.request_number ?? ""}` : `difference ${difference > 0 ? "+" : ""}${difference} (${difference > 0 ? "shopper pays" : "refund due"})`,
|
|
1852
|
+
link: null
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
var return_prime_default = {
|
|
1856
|
+
id: "return-prime",
|
|
1857
|
+
urlKey: "return_prime",
|
|
1858
|
+
label: "Return Prime",
|
|
1859
|
+
events: EVENTS3,
|
|
1860
|
+
defaultEvent: DEFAULT_EVENT3,
|
|
1861
|
+
eventType: "return_prime_return_requested",
|
|
1862
|
+
gate: { path: "request.request_type", value: "return" },
|
|
1863
|
+
gateFor: (eventName) => ({ path: "request.status", value: eventDef2(eventName).status }),
|
|
1864
|
+
signatureScheme: "none documented \u2014 every request is accepted",
|
|
1865
|
+
signatureHeader: null,
|
|
1866
|
+
fieldMap: fieldMap6,
|
|
1867
|
+
fieldMapFor,
|
|
1868
|
+
editableFields: editableFields6,
|
|
1869
|
+
editableFieldsFor: editableFieldsFor3,
|
|
1870
|
+
selectionFor: selectionFor3,
|
|
1871
|
+
eventFromPayload: eventFromPayload3,
|
|
1872
|
+
buildPayload: buildPayload6,
|
|
1873
|
+
sign: sign6,
|
|
1874
|
+
webhookUrl: webhookUrl6,
|
|
1875
|
+
webhookSecret: webhookSecret6,
|
|
1876
|
+
summarize: summarize6
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
// src/providers/fastrr.mjs
|
|
1880
|
+
var GATE_PATH4 = "event_name";
|
|
1881
|
+
var GATE_VALUE3 = "ABANDON_CART";
|
|
1882
|
+
var fieldMap7 = [
|
|
1883
|
+
{ source: "event_name", target: "\xABgate\xBB", required: true, note: "must be ABANDON_CART or the event is dropped" },
|
|
1884
|
+
{ source: "event_id", target: "eventId" },
|
|
1885
|
+
{ source: "event_data.phone_number", target: "phone", required: true, note: "BARE national number \u2014 region comes from the address" },
|
|
1886
|
+
{ source: "event_data.shipping_address.country_code", target: "phone region", required: true },
|
|
1887
|
+
{ source: "event_data.checkout_url", target: "checkoutUrl", required: true },
|
|
1888
|
+
{ source: "event_data.items[].name", target: "cart.productName", required: true, note: "the first PAID line wins; freebies rank last" },
|
|
1889
|
+
{ source: "event_data.items[].img_url", target: "cart.image" },
|
|
1890
|
+
{ source: "event_data.items[].price", target: "\u2014 (ranking only)", note: "a zero price marks the line a freebie" },
|
|
1891
|
+
{ source: "event_data.items[].sku", target: "cart.sku" },
|
|
1892
|
+
{ source: "event_data.item_count", target: "cart.itemCount" },
|
|
1893
|
+
{ source: "event_data.total_price", target: "pricing.totalPrice" },
|
|
1894
|
+
{ source: "event_data.total_discount", target: "pricing.totalDiscount" },
|
|
1895
|
+
{ source: "event_data.shipping_price", target: "pricing.shippingPrice" },
|
|
1896
|
+
{ source: "event_data.tax", target: "pricing.tax" },
|
|
1897
|
+
{ source: "event_data.currency", target: "pricing.currency" },
|
|
1898
|
+
{ source: "event_data.discount_codes[0]", target: "pricing.discountCode" },
|
|
1899
|
+
{ source: "event_data.custom_attributes.landing_page_url", target: "attribution.landingPage + utm.*", note: "utm is PARSED from this query string" },
|
|
1900
|
+
{ source: "event_data.latest_stage", target: "latestStage" },
|
|
1901
|
+
{ source: "event_data.payment_status", target: "paymentStatus" },
|
|
1902
|
+
{ source: "event_data.rtoPredict", target: "rtoPredict" },
|
|
1903
|
+
{ source: "event_data.cart_id", target: "cartId" },
|
|
1904
|
+
{ source: "event_data.cart_token", target: "cartToken" }
|
|
1905
|
+
];
|
|
1906
|
+
var editableFields7 = [
|
|
1907
|
+
{ group: "Event", path: "event_id", label: "Event id" },
|
|
1908
|
+
{ group: "Cart", path: "event_data.cart_id", label: "Cart id" },
|
|
1909
|
+
{ group: "Cart", path: "event_data.cart_token", label: "Cart token" },
|
|
1910
|
+
{ group: "Cart", path: "event_data.checkout_url", label: "Checkout URL", hint: "the link the shopper is sent back to" },
|
|
1911
|
+
{ group: "Cart", path: "event_data.latest_stage", label: "Latest stage" },
|
|
1912
|
+
{ group: "Cart", path: "event_data.payment_status", label: "Payment status" },
|
|
1913
|
+
{ group: "Cart", path: "event_data.rtoPredict", label: "RTO prediction", optional: true },
|
|
1914
|
+
{ group: "Pricing", path: "event_data.total_price", label: "Total price", type: "number" },
|
|
1915
|
+
{ group: "Pricing", path: "event_data.total_discount", label: "Discount", type: "number" },
|
|
1916
|
+
{ group: "Pricing", path: "event_data.shipping_price", label: "Shipping", type: "number" },
|
|
1917
|
+
{ group: "Pricing", path: "event_data.tax", label: "Tax", type: "number" },
|
|
1918
|
+
{ group: "Pricing", path: "event_data.currency", label: "Currency" },
|
|
1919
|
+
{ group: "Customer", path: "event_data.first_name", label: "First name" },
|
|
1920
|
+
{ group: "Customer", path: "event_data.last_name", label: "Last name" },
|
|
1921
|
+
{ group: "Customer", path: "event_data.email", label: "Email" },
|
|
1922
|
+
{ group: "Customer", path: "event_data.phone_number", label: "Phone", hint: "bare national number, no country code" },
|
|
1923
|
+
{ group: "Shipping", path: "event_data.shipping_address.address1", label: "Address line 1" },
|
|
1924
|
+
{ group: "Shipping", path: "event_data.shipping_address.city", label: "City" },
|
|
1925
|
+
{ group: "Shipping", path: "event_data.shipping_address.state", label: "State" },
|
|
1926
|
+
{ group: "Shipping", path: "event_data.shipping_address.zip", label: "Zip / postcode" },
|
|
1927
|
+
{ group: "Shipping", path: "event_data.shipping_address.country_code", label: "Country code", hint: "region used to parse the phone" },
|
|
1928
|
+
{ group: "Attribution", path: "event_data.custom_attributes.landing_page_url", label: "Landing page URL", hint: "utm parameters are read out of this query string" }
|
|
1929
|
+
];
|
|
1930
|
+
function buildPayload7(ctx) {
|
|
1931
|
+
const { config, items, phone } = ctx;
|
|
1932
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
1933
|
+
const currency = ctx.currency || config.defaults.currency;
|
|
1934
|
+
const domain = config.shopify.domain;
|
|
1935
|
+
const cust = config.customer;
|
|
1936
|
+
const nationalPhone = nationalNumber(phone, cust.countryCode);
|
|
1937
|
+
const lineItems = (items ?? []).map(({ product, variant, quantity }) => ({
|
|
1938
|
+
sku: variant?.sku || "",
|
|
1939
|
+
name: product.title,
|
|
1940
|
+
title: product.title,
|
|
1941
|
+
price: money2(variant?.price ?? 0),
|
|
1942
|
+
quantity,
|
|
1943
|
+
product_id: Number(product.id),
|
|
1944
|
+
variant_id: Number(variant?.id ?? 0),
|
|
1945
|
+
img_url: imageForVariant(product, variant),
|
|
1946
|
+
custom_attributes: { contentId: String(product.id) }
|
|
1947
|
+
}));
|
|
1948
|
+
const subtotal = money2(lineItems.reduce((sum, li) => sum + li.price * li.quantity, 0));
|
|
1949
|
+
const discount = money2(Math.min(ctx.discount ?? 0, subtotal));
|
|
1950
|
+
const shippingPrice = ctx.shippingPrice ?? 0;
|
|
1951
|
+
const total = money2(subtotal - discount + shippingPrice);
|
|
1952
|
+
const landingPage = new URL(`https://${domain}/products/${items?.[0]?.product?.handle ?? ""}`);
|
|
1953
|
+
for (const [key, value] of Object.entries(config.defaults.utm ?? {})) {
|
|
1954
|
+
if (value) landingPage.searchParams.set(key, String(value));
|
|
1955
|
+
}
|
|
1956
|
+
const address = {
|
|
1957
|
+
zip: cust.zip,
|
|
1958
|
+
city: cust.city,
|
|
1959
|
+
state: cust.province,
|
|
1960
|
+
name: `${cust.firstName} ${cust.lastName}`.trim(),
|
|
1961
|
+
phone: nationalPhone,
|
|
1962
|
+
country: cust.country,
|
|
1963
|
+
address1: cust.address1,
|
|
1964
|
+
last_name: cust.lastName,
|
|
1965
|
+
first_name: cust.firstName,
|
|
1966
|
+
country_code: cust.countryCode,
|
|
1967
|
+
line_1: cust.address1,
|
|
1968
|
+
email: cust.email
|
|
1969
|
+
};
|
|
1970
|
+
const cartToken = `fastrr-${now.getTime()}`;
|
|
1971
|
+
return {
|
|
1972
|
+
event_id: `fr-${now.getTime()}`,
|
|
1973
|
+
event_name: GATE_VALUE3,
|
|
1974
|
+
event_data: {
|
|
1975
|
+
checkout_url: cartPermalink(domain, (items ?? []).map(({ variant, quantity }) => ({ variantId: variant?.id, quantity }))),
|
|
1976
|
+
total_discount: discount,
|
|
1977
|
+
latest_stage: "ORDER_SCREEN",
|
|
1978
|
+
product_id_list: lineItems.map((li) => String(li.product_id)),
|
|
1979
|
+
billing_address: address,
|
|
1980
|
+
custom_attributes: {
|
|
1981
|
+
landing_page_url: landingPage.toString(),
|
|
1982
|
+
ipv4_address: "192.168.1.1",
|
|
1983
|
+
shopifyCartToken: cartToken
|
|
1984
|
+
},
|
|
1985
|
+
cart_id: cartToken,
|
|
1986
|
+
updated_at: now.toISOString().replace("Z", ""),
|
|
1987
|
+
currency,
|
|
1988
|
+
shipping_address: address,
|
|
1989
|
+
first_name: cust.firstName,
|
|
1990
|
+
email: cust.email,
|
|
1991
|
+
source_name: "fastrr",
|
|
1992
|
+
item_count: lineItems.length,
|
|
1993
|
+
shipping_price: shippingPrice,
|
|
1994
|
+
variant_id_list: lineItems.map((li) => String(li.variant_id)),
|
|
1995
|
+
total_price: total,
|
|
1996
|
+
payment_status: "Pending",
|
|
1997
|
+
last_name: cust.lastName,
|
|
1998
|
+
tax: 0,
|
|
1999
|
+
item_title_list: lineItems.map((li) => li.title),
|
|
2000
|
+
item_name_list: lineItems.map((li) => li.name),
|
|
2001
|
+
img_url: lineItems[0]?.img_url,
|
|
2002
|
+
sku_list: lineItems.map((li) => li.sku),
|
|
2003
|
+
item_price_list: lineItems.map((li) => String(li.price)),
|
|
2004
|
+
cart_token: cartToken,
|
|
2005
|
+
phone_number: nationalPhone,
|
|
2006
|
+
items: lineItems,
|
|
2007
|
+
discount_codes: ctx.couponCode ? [ctx.couponCode] : [],
|
|
2008
|
+
rtoPredict: "low"
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
function sign7() {
|
|
2013
|
+
return { signature: null, timestamp: null, headers: { "content-type": "application/json" } };
|
|
2014
|
+
}
|
|
2015
|
+
function webhookUrl7(config) {
|
|
2016
|
+
return String(targetFor(config, "fastrr").webhookUrl ?? "").trim();
|
|
2017
|
+
}
|
|
2018
|
+
function webhookSecret7(config) {
|
|
2019
|
+
return String(targetFor(config, "fastrr").webhookSecret ?? "");
|
|
2020
|
+
}
|
|
2021
|
+
function summarize7(payload) {
|
|
2022
|
+
const d = payload?.event_data ?? {};
|
|
2023
|
+
let store;
|
|
2024
|
+
try {
|
|
2025
|
+
store = d.checkout_url ? new URL(d.checkout_url).host : void 0;
|
|
2026
|
+
} catch {
|
|
2027
|
+
store = d.checkout_url;
|
|
2028
|
+
}
|
|
2029
|
+
const items = Array.isArray(d.items) ? d.items : [];
|
|
2030
|
+
return {
|
|
2031
|
+
event: "abandoned cart",
|
|
2032
|
+
store,
|
|
2033
|
+
customer: `${d.first_name ?? ""} ${d.last_name ?? ""} \xB7 ${d.phone_number ?? ""}`.trim(),
|
|
2034
|
+
items: items.map((i) => `${i.name} x${i.quantity}`).join(", "),
|
|
2035
|
+
total: d.total_price,
|
|
2036
|
+
currency: d.currency,
|
|
2037
|
+
extra: `stage ${d.latest_stage}, rto ${d.rtoPredict}`,
|
|
2038
|
+
link: d.checkout_url
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
var fastrr_default = {
|
|
2042
|
+
id: "fastrr",
|
|
2043
|
+
urlKey: "fastrr",
|
|
2044
|
+
label: "Fastrr",
|
|
2045
|
+
eventType: "fastrr_abandoned_cart",
|
|
2046
|
+
gate: { path: GATE_PATH4, value: GATE_VALUE3 },
|
|
2047
|
+
signatureScheme: "none documented \u2014 every request is accepted",
|
|
2048
|
+
signatureHeader: null,
|
|
2049
|
+
fieldMap: fieldMap7,
|
|
2050
|
+
editableFields: editableFields7,
|
|
2051
|
+
buildPayload: buildPayload7,
|
|
2052
|
+
sign: sign7,
|
|
2053
|
+
webhookUrl: webhookUrl7,
|
|
2054
|
+
webhookSecret: webhookSecret7,
|
|
2055
|
+
summarize: summarize7
|
|
2056
|
+
};
|
|
2057
|
+
|
|
1075
2058
|
// src/providers/index.mjs
|
|
1076
2059
|
var PROVIDER_META = [
|
|
1077
2060
|
{
|
|
@@ -1095,12 +2078,46 @@ var PROVIDER_META = [
|
|
|
1095
2078
|
header: "authorization",
|
|
1096
2079
|
available: true,
|
|
1097
2080
|
events: nitro_default.events
|
|
2081
|
+
},
|
|
2082
|
+
{
|
|
2083
|
+
id: "shopflo",
|
|
2084
|
+
label: "Shopflo",
|
|
2085
|
+
signature: "none documented \u2014 every request is accepted",
|
|
2086
|
+
header: null,
|
|
2087
|
+
available: true,
|
|
2088
|
+
events: shopflo_default.events
|
|
2089
|
+
},
|
|
2090
|
+
{
|
|
2091
|
+
id: "flexype",
|
|
2092
|
+
label: "FlexyPe",
|
|
2093
|
+
signature: "static shared secret in the header",
|
|
2094
|
+
header: "x-flexype-authorization",
|
|
2095
|
+
available: true
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
id: "return-prime",
|
|
2099
|
+
label: "Return Prime",
|
|
2100
|
+
signature: "none documented \u2014 every request is accepted",
|
|
2101
|
+
header: null,
|
|
2102
|
+
available: true,
|
|
2103
|
+
events: return_prime_default.events
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
id: "fastrr",
|
|
2107
|
+
label: "Fastrr",
|
|
2108
|
+
signature: "none documented \u2014 every request is accepted",
|
|
2109
|
+
header: null,
|
|
2110
|
+
available: true
|
|
1098
2111
|
}
|
|
1099
2112
|
];
|
|
1100
2113
|
var providers = {
|
|
1101
2114
|
[cashfree_occ_default.id]: cashfree_occ_default,
|
|
1102
2115
|
[razorpay_magic_default.id]: razorpay_magic_default,
|
|
1103
|
-
[nitro_default.id]: nitro_default
|
|
2116
|
+
[nitro_default.id]: nitro_default,
|
|
2117
|
+
[shopflo_default.id]: shopflo_default,
|
|
2118
|
+
[flexype_default.id]: flexype_default,
|
|
2119
|
+
[return_prime_default.id]: return_prime_default,
|
|
2120
|
+
[fastrr_default.id]: fastrr_default
|
|
1104
2121
|
};
|
|
1105
2122
|
function getProvider(id) {
|
|
1106
2123
|
const provider = providers[id];
|
|
@@ -1466,74 +2483,6 @@ function autoSelect(products, count = 1) {
|
|
|
1466
2483
|
}));
|
|
1467
2484
|
}
|
|
1468
2485
|
|
|
1469
|
-
// src/phone.mjs
|
|
1470
|
-
var CALLING_CODES = {
|
|
1471
|
-
IN: "91",
|
|
1472
|
-
US: "1",
|
|
1473
|
-
CA: "1",
|
|
1474
|
-
GB: "44",
|
|
1475
|
-
AE: "971",
|
|
1476
|
-
SG: "65",
|
|
1477
|
-
AU: "61",
|
|
1478
|
-
NZ: "64",
|
|
1479
|
-
DE: "49",
|
|
1480
|
-
FR: "33",
|
|
1481
|
-
IT: "39",
|
|
1482
|
-
ES: "34",
|
|
1483
|
-
NL: "31",
|
|
1484
|
-
SE: "46",
|
|
1485
|
-
RO: "40",
|
|
1486
|
-
PL: "48",
|
|
1487
|
-
ZA: "27",
|
|
1488
|
-
NG: "234",
|
|
1489
|
-
KE: "254",
|
|
1490
|
-
BD: "880",
|
|
1491
|
-
PK: "92",
|
|
1492
|
-
LK: "94",
|
|
1493
|
-
NP: "977",
|
|
1494
|
-
MY: "60",
|
|
1495
|
-
ID: "62",
|
|
1496
|
-
PH: "63",
|
|
1497
|
-
TH: "66",
|
|
1498
|
-
VN: "84",
|
|
1499
|
-
JP: "81",
|
|
1500
|
-
KR: "82",
|
|
1501
|
-
CN: "86",
|
|
1502
|
-
SA: "966",
|
|
1503
|
-
QA: "974",
|
|
1504
|
-
KW: "965",
|
|
1505
|
-
OM: "968",
|
|
1506
|
-
BH: "973",
|
|
1507
|
-
BR: "55",
|
|
1508
|
-
MX: "52"
|
|
1509
|
-
};
|
|
1510
|
-
function callingCode(regionCode) {
|
|
1511
|
-
return CALLING_CODES[String(regionCode || "").toUpperCase()] || null;
|
|
1512
|
-
}
|
|
1513
|
-
function toE164(raw, regionCode) {
|
|
1514
|
-
const cleaned = String(raw ?? "").replace(/\s+/g, "");
|
|
1515
|
-
if (!cleaned) return { ok: false, reason: "phone is empty" };
|
|
1516
|
-
if (cleaned.startsWith("+")) {
|
|
1517
|
-
const digits2 = cleaned.slice(1).replace(/\D/g, "");
|
|
1518
|
-
if (digits2.length < 8 || digits2.length > 15) return { ok: false, reason: `+${digits2} is not a plausible E.164 length` };
|
|
1519
|
-
return { ok: true, value: `+${digits2}` };
|
|
1520
|
-
}
|
|
1521
|
-
const cc = callingCode(regionCode);
|
|
1522
|
-
if (!cc) {
|
|
1523
|
-
return {
|
|
1524
|
-
ok: false,
|
|
1525
|
-
reason: `no country calling code known for region "${regionCode}" \u2014 give the phone in +E.164 form instead`
|
|
1526
|
-
};
|
|
1527
|
-
}
|
|
1528
|
-
let national = cleaned.replace(/\D/g, "").replace(/^0+/, "");
|
|
1529
|
-
if (national.startsWith(cc) && national.length > 10) national = national.slice(cc.length);
|
|
1530
|
-
const value = `+${cc}${national}`;
|
|
1531
|
-
const digits = value.slice(1);
|
|
1532
|
-
if (digits.length < 8 || digits.length > 15) return { ok: false, reason: `${value} is not a plausible E.164 length` };
|
|
1533
|
-
return { ok: true, value };
|
|
1534
|
-
}
|
|
1535
|
-
var knownRegions = Object.keys(CALLING_CODES);
|
|
1536
|
-
|
|
1537
2486
|
// src/core/paths.mjs
|
|
1538
2487
|
function getPath(obj, path6) {
|
|
1539
2488
|
return String(path6).replace(/\[(\d+)\]/g, ".$1").split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
|
|
@@ -1701,7 +2650,19 @@ function locatePhone(payload) {
|
|
|
1701
2650
|
["phone", "customer.Shipping_address.Country_code"],
|
|
1702
2651
|
["customer.Shipping_address.Phone", "customer.Shipping_address.Country_code"],
|
|
1703
2652
|
// nitro
|
|
1704
|
-
["eventVal.customer.phone", "country"]
|
|
2653
|
+
["eventVal.customer.phone", "country"],
|
|
2654
|
+
// shopflo — one path per envelope, most specific first
|
|
2655
|
+
["eventPayload.userData.phone", "eventPayload.clientData.country"],
|
|
2656
|
+
["data.user_data.phone", "data.country_code"],
|
|
2657
|
+
["customer.phone", "customer.country_code"],
|
|
2658
|
+
// flexype
|
|
2659
|
+
["payload.user.phone_with_dial_code", "payload.checkout_context.presentment_country"],
|
|
2660
|
+
["payload.user.phone", "payload.checkout_context.presentment_country"],
|
|
2661
|
+
// return prime — wrapped and root envelopes
|
|
2662
|
+
["request.customer.phone", "request.customer.address.country_code"],
|
|
2663
|
+
// fastrr — a bare national number, so the region is what makes it parse
|
|
2664
|
+
["event_data.phone_number", "event_data.shipping_address.country_code"],
|
|
2665
|
+
["event_data.billing_address.phone", "event_data.billing_address.country_code"]
|
|
1705
2666
|
];
|
|
1706
2667
|
for (const [phonePath, regionPath] of candidates) {
|
|
1707
2668
|
const phone = getPath(payload, phonePath);
|
|
@@ -1736,8 +2697,8 @@ function checkAllowedPhone(payload, config) {
|
|
|
1736
2697
|
detail: ok ? `${normalized} is an approved test handset` : `${normalized} is NOT in allowedPhones \u2014 refusing to risk messaging a real shopper`
|
|
1737
2698
|
};
|
|
1738
2699
|
}
|
|
1739
|
-
function coverage(payload,
|
|
1740
|
-
return
|
|
2700
|
+
function coverage(payload, fieldMap8) {
|
|
2701
|
+
return fieldMap8.map((f) => {
|
|
1741
2702
|
const value = getPath(payload, f.source);
|
|
1742
2703
|
const present = value !== void 0 && value !== null && value !== "";
|
|
1743
2704
|
return {
|
|
@@ -1864,7 +2825,7 @@ function loadPayload(file) {
|
|
|
1864
2825
|
}
|
|
1865
2826
|
|
|
1866
2827
|
// src/core/build.mjs
|
|
1867
|
-
function draftPayload({ cfg, providerId = "cashfree-occ", items, discount = 0, phone, eventName, collection }) {
|
|
2828
|
+
function draftPayload({ cfg, providerId = "cashfree-occ", items, discount = 0, phone, eventName, collection, envelope }) {
|
|
1868
2829
|
const provider = getProvider(providerId);
|
|
1869
2830
|
const event = eventName ?? provider.defaultEvent;
|
|
1870
2831
|
const resolvedPhone = phone ?? cfg.customer.phone;
|
|
@@ -1875,6 +2836,7 @@ function draftPayload({ cfg, providerId = "cashfree-occ", items, discount = 0, p
|
|
|
1875
2836
|
discount,
|
|
1876
2837
|
eventName: event,
|
|
1877
2838
|
collection,
|
|
2839
|
+
envelope,
|
|
1878
2840
|
phone: parsed.ok ? parsed.value : String(resolvedPhone ?? "")
|
|
1879
2841
|
});
|
|
1880
2842
|
const editable = provider.editableFieldsFor ? provider.editableFieldsFor(event) : provider.editableFields;
|
|
@@ -1890,7 +2852,7 @@ function applyEdits(payload, fields, edits) {
|
|
|
1890
2852
|
}
|
|
1891
2853
|
return payload;
|
|
1892
2854
|
}
|
|
1893
|
-
async function
|
|
2855
|
+
async function buildPayload8({ cfg, providerId = "cashfree-occ", items = [], discount = 0, phone, checkImageUrls = true, payload: prebuilt, eventName, collection, envelope }) {
|
|
1894
2856
|
const provider0 = getProvider(providerId);
|
|
1895
2857
|
const needsItems = !provider0.selectionFor || ["cart", "product"].includes(provider0.selectionFor(eventName ?? provider0.defaultEvent));
|
|
1896
2858
|
if (needsItems && !items.length) throw new ConfigError("no products selected");
|
|
@@ -1899,7 +2861,7 @@ async function buildPayload4({ cfg, providerId = "cashfree-occ", items = [], dis
|
|
|
1899
2861
|
const parsed = toE164(resolvedPhone, cfg.customer.countryCode);
|
|
1900
2862
|
if (!parsed.ok) throw new ConfigError(`phone is unusable: ${parsed.reason}`);
|
|
1901
2863
|
const event = eventName ?? provider.defaultEvent;
|
|
1902
|
-
const payload = prebuilt ?? provider.buildPayload({ config: cfg, items, discount, phone: parsed.value, eventName: event, collection });
|
|
2864
|
+
const payload = prebuilt ?? provider.buildPayload({ config: cfg, items, discount, phone: parsed.value, eventName: event, collection, envelope });
|
|
1903
2865
|
const body = JSON.stringify(payload);
|
|
1904
2866
|
const report = await runAll({ payload, body, provider, config: cfg, checkImageUrls, eventName: event });
|
|
1905
2867
|
const summary = provider.summarize ? provider.summarize(payload) : {};
|
|
@@ -2323,7 +3285,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
2323
3285
|
const onDiscount = (raw) => startReview(Math.max(0, Number.parseFloat(raw) || 0));
|
|
2324
3286
|
const finalise = (payload) => {
|
|
2325
3287
|
setStep(STEP.BUILDING);
|
|
2326
|
-
|
|
3288
|
+
buildPayload8({ cfg: config, providerId, items, discount: draft.discount, payload, eventName, collection }).then(async (result) => {
|
|
2327
3289
|
setBuilt(result);
|
|
2328
3290
|
let req = null;
|
|
2329
3291
|
let command = "";
|
|
@@ -3536,18 +4498,43 @@ async function cmdBuild(args, config) {
|
|
|
3536
4498
|
const discount = Number.parseFloat(args.flags.discount ?? "0") || 0;
|
|
3537
4499
|
const search = typeof args.flags.search === "string" ? args.flags.search : void 0;
|
|
3538
4500
|
const checkImageUrls = args.flags.image !== false && args.flags["image-check"] !== false;
|
|
3539
|
-
|
|
3540
|
-
const
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
4501
|
+
const provider = getProvider(providerId);
|
|
4502
|
+
const eventName = typeof args.flags.event === "string" ? args.flags.event : provider.defaultEvent;
|
|
4503
|
+
const selection = provider.selectionFor ? provider.selectionFor(eventName) : "cart";
|
|
4504
|
+
let items = [];
|
|
4505
|
+
let collection;
|
|
4506
|
+
if (selection === "collection") {
|
|
4507
|
+
log.step(`fetching collections from ${config.shopify.domain}\u2026`);
|
|
4508
|
+
const collections = await fetchCollections(config, { search });
|
|
4509
|
+
if (!collections.length) {
|
|
4510
|
+
log.fail("Shopify returned no collections");
|
|
4511
|
+
log.dim("create one in Shopify, or pick a different event");
|
|
4512
|
+
return 1;
|
|
4513
|
+
}
|
|
4514
|
+
const wanted = typeof args.flags.collection === "string" ? args.flags.collection.toLowerCase() : null;
|
|
4515
|
+
collection = collections.find((c2) => wanted && (c2.id === wanted || c2.handle === wanted || c2.title.toLowerCase() === wanted)) ?? collections[0];
|
|
4516
|
+
if (wanted && collection.title.toLowerCase() !== wanted && collection.id !== wanted && collection.handle !== wanted) {
|
|
4517
|
+
log.warn(`no collection matched "${args.flags.collection}", using ${collection.title}`);
|
|
4518
|
+
}
|
|
4519
|
+
log.ok(`collection: ${collection.title}`);
|
|
4520
|
+
} else if (selection !== "none") {
|
|
4521
|
+
log.step(`fetching products from ${config.shopify.domain}\u2026`);
|
|
4522
|
+
const products = await fetchCatalogue(config, { search });
|
|
4523
|
+
if (!products.length) {
|
|
4524
|
+
log.fail("Shopify returned no products");
|
|
4525
|
+
return 1;
|
|
4526
|
+
}
|
|
4527
|
+
items = autoSelect(products, selection === "product" ? 1 : count);
|
|
4528
|
+
log.ok(`${items.length} product(s): ${items.map((i) => i.product.title).join(", ")}`);
|
|
4529
|
+
} else {
|
|
4530
|
+
log.ok(`${eventName} needs no product or collection`);
|
|
3544
4531
|
}
|
|
3545
|
-
const
|
|
3546
|
-
log.ok(`${items.length} product(s): ${items.map((i) => i.product.title).join(", ")}`);
|
|
3547
|
-
const built = await buildPayload4({
|
|
4532
|
+
const built = await buildPayload8({
|
|
3548
4533
|
cfg: config,
|
|
3549
4534
|
providerId,
|
|
3550
|
-
eventName
|
|
4535
|
+
eventName,
|
|
4536
|
+
collection,
|
|
4537
|
+
envelope: typeof args.flags.envelope === "string" ? args.flags.envelope : void 0,
|
|
3551
4538
|
items,
|
|
3552
4539
|
discount,
|
|
3553
4540
|
phone: typeof args.flags.phone === "string" ? args.flags.phone : void 0,
|
|
@@ -3750,9 +4737,18 @@ ${c.bold("COMMANDS")}
|
|
|
3750
4737
|
help show this message
|
|
3751
4738
|
|
|
3752
4739
|
${c.bold("BUILD OPTIONS")}
|
|
3753
|
-
--provider <id> cashfree-occ
|
|
3754
|
-
|
|
3755
|
-
|
|
4740
|
+
--provider <id> cashfree-occ \xB7 razorpay-magic \xB7 nitro \xB7 shopflo
|
|
4741
|
+
flexype \xB7 return-prime \xB7 fastrr (default cashfree-occ)
|
|
4742
|
+
--event <name> multi-event providers only:
|
|
4743
|
+
nitro view \xB7 category_view \xB7 product_view \xB7 addtocart
|
|
4744
|
+
removefromcart \xB7 checkout \xB7 orders/create \xB7 orders/updated
|
|
4745
|
+
shopflo store_page_view \xB7 collection_page_viewed \xB7 product_page_viewed
|
|
4746
|
+
added_to_cart_ui \xB7 checkout_clicked \xB7 checkout_abandoned
|
|
4747
|
+
order_completed
|
|
4748
|
+
return-prime {return,exchange}_{requested,approved,received,
|
|
4749
|
+
inspected,rejected}
|
|
4750
|
+
--envelope <x> return-prime only: wrapped (default) or root
|
|
4751
|
+
--collection <x> category or collection view: collection id, handle or title
|
|
3756
4752
|
--items <n> how many products to put in the cart (default 1)
|
|
3757
4753
|
--search <text> only consider products matching a title
|
|
3758
4754
|
--discount <n> cart discount in major currency units (default 0)
|