@devx-commerce/plugin-loyalty
Version:
A starter for Medusa plugins.
397 lines • 34.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.config = void 0;
exports.default = loyaltyBulkImportHandler;
const utils_1 = require("@medusajs/framework/utils");
const core_flows_1 = require("@medusajs/medusa/core-flows");
const sync_1 = require("csv-parse/sync");
const phone_formater_1 = require("../lib/phone-formater");
const import_feed_notification_1 = require("../lib/import-feed-notification");
const manage_points_1 = require("../workflows/points/manage-points");
// Canonical import columns, echoed back (with original values) in the error CSV, plus a
// trailing Error column — matching the edd/review/reship import convention so Ops can fix
// the failed rows in place and re-upload.
const ERROR_CSV_COLUMNS = [
"order_id",
"phone",
"email",
"points",
"l1_tag",
"l2_tag",
"reason",
"expiry_days",
];
// `points`, `l1_tag` and `l2_tag` are always required; at least one identifier column must exist too.
const REQUIRED_HEADERS = ["points", "l1_tag", "l2_tag"];
const IDENTIFIER_HEADERS = ["phone", "order_id", "email"];
// Allowed L1/L2 tags are read from this host-app (twt-backend) config module + key.
const GLOBAL_CONFIG_MODULE = "global_config";
const INTERNAL_NOTES_CONFIG_KEY = "internal_notes_for_manual_order";
// Each row runs a workflow (with a per-customer lock inside), so cap concurrency.
const WRITE_BATCH_SIZE = 8;
// ---------------------------- cell parsers ----------------------------
// Trim a cell; treat empty/whitespace as absent.
function cell(v) {
if (v == null)
return undefined;
const t = v.trim();
return t === "" ? undefined : t;
}
function parsePositiveInt(v, field) {
const t = cell(v);
if (t === undefined)
throw new Error(`Missing required value "${field}"`);
const n = Number(t);
if (!Number.isInteger(n) || n <= 0)
throw new Error(`${field} must be a positive integer (got "${t}")`);
return n;
}
// expiry_days: blank -> 365 (default validity); otherwise a positive integer number of days.
const DEFAULT_EXPIRY_DAYS = 365;
function parseExpiryDays(v) {
const t = cell(v);
if (t === undefined)
return DEFAULT_EXPIRY_DAYS;
const n = Number(t);
if (!Number.isInteger(n) || n <= 0)
throw new Error(`expiry_days must be a positive integer or blank (got "${t}")`);
return n;
}
function dropUndefined(obj) {
const out = {};
for (const [k, v] of Object.entries(obj)) {
if (v !== undefined)
out[k] = v;
}
return out;
}
// ---------------------------- customer resolution ----------------------------
// Resolve a customer_id from a row using the priority order_id -> phone -> email.
// A higher-priority identifier that is present but doesn't resolve falls through to the
// next present one; if none resolve, throws with every reason collected so the error CSV
// tells Ops exactly why. Matches guest and account-holding customers alike; credits only on
// exactly one match, otherwise reports the row as ambiguous (never guesses).
async function resolveCustomer(query, row) {
const phone = cell(row.phone);
const orderId = cell(row.order_id);
const email = cell(row.email);
if (!phone && !orderId && !email) {
throw new Error("No identifier provided (need one of phone, order_id, email)");
}
const reasons = [];
if (orderId) {
// Accept either the internal Medusa id (order_01…) or the human order number
// (display_id, e.g. "21295350"): order_-prefixed -> id; all-digits -> display_id;
// otherwise fall back to id.
const filters = orderId.startsWith("order_")
? { id: orderId }
: /^\d+$/.test(orderId)
? { display_id: Number(orderId) }
: { id: orderId };
const { data } = await query.graph({
entity: "order",
fields: ["id", "customer_id"],
filters,
});
const customerId = data[0]?.customer_id;
if (customerId)
return { customer_id: customerId, matched_by: "order_id", matched_value: orderId };
reasons.push(data.length === 0
? `no order found for order_id ${orderId}`
: `order ${orderId} has no customer`);
}
if (phone) {
const normalizedPhone = (0, phone_formater_1.formatPhoneNumber)(phone);
if (normalizedPhone === "Invalid phone number") {
reasons.push(`phone "${phone}" is not a valid number`);
}
else {
const { data } = await query.graph({
entity: "customer",
fields: ["id"],
filters: { phone: normalizedPhone },
});
if (data.length === 1)
return { customer_id: data[0].id, matched_by: "phone", matched_value: normalizedPhone };
reasons.push(data.length === 0
? `no customer found for phone ${normalizedPhone}`
: `phone ${normalizedPhone} matches ${data.length} customers`);
}
}
if (email) {
const { data } = await query.graph({
entity: "customer",
fields: ["id"],
filters: { email },
});
if (data.length === 1)
return { customer_id: data[0].id, matched_by: "email", matched_value: email };
reasons.push(data.length === 0
? `no customer found for email ${email}`
: `email ${email} matches ${data.length} customers`);
}
throw new Error(`Could not resolve customer — ${reasons.join("; ")}`);
}
// l1_tag / l2_tag -> transaction notes, in the same shape the admin manual-credit form
// produces: { [l1_category]: [l2_tag] }. Both are mandatory (validated in creditRow).
function buildNotes(l1, l2) {
return { [l1]: [l2] };
}
// Match a row's l1/l2 against the allowed set, case-insensitively, and return the
// canonically-cased tags to persist (so "general query" is stored as the defined
// "General query"). "Custom" (any casing) is an escape-hatch L1 (any L2); every other L1
// must be defined and its L2 must belong to that L1. Only casing is forgiven — a genuine
// wrong/misspelled tag still fails.
function resolveTags(l1, l2, allowed) {
if (l1.toLowerCase() === "custom")
return { l1: "Custom", l2 };
const resolvedL1 = allowed.l1.find((v) => v.toLowerCase() === l1.toLowerCase());
if (!resolvedL1)
throw new Error(`l1_tag "${l1}" is not an allowed tag`);
const resolvedL2 = allowed.l2[resolvedL1]?.find((v) => v.toLowerCase() === l2.toLowerCase());
if (!resolvedL2)
throw new Error(`l2_tag "${l2}" is not allowed under l1_tag "${resolvedL1}"`);
return { l1: resolvedL1, l2: resolvedL2 };
}
// Credit a single row. reference_type is always "manual" and the order_id is NOT stamped on
// the transaction (it lives in metadata only) so imported points are permanent and never
// auto-reversed by the order-return flow (which matches on the transaction's order_id).
async function creditRow(container, query, row, allowedTags) {
const points = parsePositiveInt(row.points, "points");
const expiry_days = parseExpiryDays(row.expiry_days);
// L1/L2 tags are mandatory, and must be from the defined internal-notes set.
const l1 = cell(row.l1_tag);
const l2 = cell(row.l2_tag);
if (!l1)
throw new Error('Missing required value "l1_tag"');
if (!l2)
throw new Error('Missing required value "l2_tag"');
const { l1: resolvedL1, l2: resolvedL2 } = resolveTags(l1, l2, allowedTags);
const { customer_id, matched_by, matched_value } = await resolveCustomer(query, row);
// reason -> customer-visible description. Blank -> undefined so the service applies its
// "Points added" default.
const description = cell(row.reason);
const metadata = dropUndefined({
import: true,
matched_by,
matched_value,
order_id: cell(row.order_id),
phone: cell(row.phone),
email: cell(row.email),
});
await (0, manage_points_1.managePointsWorkflow)(container).run({
input: {
customer_id,
points,
type: "credit",
reference_type: "manual",
description,
expiry_days,
metadata,
notes: buildNotes(resolvedL1, resolvedL2),
},
});
}
// ---------------------------- handler helpers ----------------------------
function validateCsvHeaders(headers) {
const missing = REQUIRED_HEADERS.filter((h) => !headers.includes(h));
if (missing.length > 0)
return { isValid: false, error: `Missing required columns: ${missing.join(", ")}` };
if (!IDENTIFIER_HEADERS.some((h) => headers.includes(h)))
return {
isValid: false,
error: `At least one identifier column required: ${IDENTIFIER_HEADERS.join(", ")}`,
};
return { isValid: true };
}
// The identifiers as supplied in the row, for the error CSV.
function rowIdentifierLabel(row) {
return ([cell(row.phone), cell(row.order_id), cell(row.email)]
.filter(Boolean)
.join(" / ") || "(none)");
}
function buildErrorCsv(errors) {
const header = ["Row", ...ERROR_CSV_COLUMNS, "Error"];
const lines = errors.map((e) => [e.row || "", ...ERROR_CSV_COLUMNS.map((c) => e.data[c] ?? ""), e.error]
.map((v) => `"${String(v).replace(/"/g, '""')}"`)
.join(","));
return [header.map((h) => `"${h}"`).join(","), ...lines].join("\n");
}
async function sendImportNotification(container, opts) {
await (0, import_feed_notification_1.sendImportFeedNotification)(container, {
template: "loyalty-import",
resourceType: "bulk_job",
triggerType: opts.status === "completed" ? "job_completed" : "job_failed",
title: opts.status === "completed"
? "Loyalty Import Completed"
: "Loyalty Import Failed",
...(opts.errorCsvUrl && { file: { url: opts.errorCsvUrl } }),
metadata: {
...opts.counts,
requested_by: opts.requestedBy,
...(opts.errorMessage && { error: opts.errorMessage }),
},
});
}
const ZERO_COUNTS = { total: 0, successful: 0, failed: 0 };
// ---------------------------- handler ----------------------------
async function loyaltyBulkImportHandler({ event, container, }) {
const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
const { s3_url, requested_by_name } = event.data;
const requestedBy = requested_by_name || "Ops";
logger.info(`Loyalty bulk import started from: ${s3_url}`);
// Download CSV from S3.
let csvContent;
try {
const response = await fetch(s3_url);
if (!response.ok)
throw new Error(`${response.status} ${response.statusText}`);
csvContent = await response.text();
}
catch (error) {
logger.error("Loyalty bulk import: failed to fetch CSV", error);
await sendImportNotification(container, {
status: "failed",
requestedBy,
counts: ZERO_COUNTS,
errorMessage: "Could not download the uploaded file.",
});
return;
}
// Parse CSV. Headers normalized to snake_case so "Order ID"/"Phone"/etc. map to the
// documented names; relax_column_count so a malformed row fails only itself; bom strips
// a leading byte-order mark.
let rows;
let headers;
try {
rows = (0, sync_1.parse)(csvContent, {
columns: (hdrs) => hdrs.map((h) => h.trim().toLowerCase().replace(/[\s-]+/g, "_")),
skip_empty_lines: true,
trim: true,
relax_column_count: true,
bom: true,
});
headers = Object.keys(rows[0] ?? {});
}
catch (error) {
logger.error("Loyalty bulk import: failed to parse CSV", error);
await sendImportNotification(container, {
status: "failed",
requestedBy,
counts: ZERO_COUNTS,
errorMessage: "The file could not be read as a CSV.",
});
return;
}
if (rows.length === 0) {
logger.error("Loyalty bulk import: CSV file is empty or contains no data rows");
await sendImportNotification(container, {
status: "failed",
requestedBy,
counts: ZERO_COUNTS,
errorMessage: "The file has no data rows.",
});
return;
}
const headerValidation = validateCsvHeaders(headers);
if (!headerValidation.isValid) {
logger.error(`Loyalty bulk import: ${headerValidation.error}`);
await sendImportNotification(container, {
status: "failed",
requestedBy,
counts: ZERO_COUNTS,
errorMessage: headerValidation.error,
});
return;
}
// Load the allowed L1/L2 tags once (from the app's internal-notes config). Fail-closed:
// if they can't be read/parsed, abort the whole import — not a single row is credited.
let allowedTags;
try {
const globalConfig = container.resolve(GLOBAL_CONFIG_MODULE);
const [notesConfigRow] = await globalConfig.listGlobalConfigs({
key: INTERNAL_NOTES_CONFIG_KEY,
});
const parsed = notesConfigRow?.value
? JSON.parse(notesConfigRow.value)
: null;
if (!parsed ||
!Array.isArray(parsed.l1) ||
typeof parsed.l2 !== "object" ||
parsed.l2 === null) {
throw new Error("internal_notes_for_manual_order config missing or malformed");
}
allowedTags = parsed;
}
catch (error) {
logger.error("Loyalty bulk import: failed to load allowed L1/L2 tags", error);
await sendImportNotification(container, {
status: "failed",
requestedBy,
counts: ZERO_COUNTS,
errorMessage: "Failed to load the allowed L1/L2 tags. Import aborted — no points were credited.",
});
return;
}
const errors = [];
let successful = 0;
logger.info(`Loyalty bulk import: processing ${rows.length} row(s)`);
for (let i = 0; i < rows.length; i += WRITE_BATCH_SIZE) {
const batch = rows.slice(i, i + WRITE_BATCH_SIZE);
await Promise.all(batch.map(async (row, j) => {
const rowNumber = i + j + 1; // 1-based data-row number (CSV header not counted)
try {
await creditRow(container, query, row, allowedTags);
successful++;
}
catch (error) {
const message = error.message || "Unknown error";
errors.push({
row: rowNumber,
data: row,
error: message,
});
logger.error(`Loyalty bulk import: row ${rowNumber} (${rowIdentifierLabel(row)}) failed — ${message}`);
}
}));
logger.info(`Loyalty bulk import: ${Math.min(i + WRITE_BATCH_SIZE, rows.length)}/${rows.length} processed`);
}
// Upload failed rows CSV to S3.
let errorCsvUrl;
if (errors.length > 0) {
try {
const { result: uploaded } = await (0, core_flows_1.uploadFilesWorkflow)(container).run({
input: {
files: [
{
filename: `loyalty-imports/errors/loyalty-errors-${Date.now()}.csv`,
mimeType: "text/csv",
content: buildErrorCsv(errors),
access: "private",
},
],
},
});
const fileService = container.resolve(utils_1.Modules.FILE);
errorCsvUrl = await fileService
.getProvider()
.getPresignedDownloadUrl({ fileKey: uploaded[0].id });
}
catch (csvError) {
logger.error("Loyalty bulk import: failed to upload error CSV", csvError);
}
}
const failed = errors.length;
await sendImportNotification(container, {
status: "completed",
requestedBy,
counts: { total: successful + failed, successful, failed },
errorCsvUrl,
});
logger.info(`Loyalty bulk import completed. Rows: ${rows.length}, Credited: ${successful}, Failed: ${failed}`);
}
exports.config = {
event: "loyalty.bulk_import",
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG95YWx0eS1idWxrLWltcG9ydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zdWJzY3JpYmVycy9sb3lhbHR5LWJ1bGstaW1wb3J0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQWtVQSwyQ0F1TEM7QUFwZkQscURBQStFO0FBQy9FLDREQUFrRTtBQUNsRSx5Q0FBdUM7QUFFdkMsMERBQTBEO0FBQzFELDhFQUE2RTtBQUM3RSxxRUFBeUU7QUFlekUsd0ZBQXdGO0FBQ3hGLDBGQUEwRjtBQUMxRiwwQ0FBMEM7QUFDMUMsTUFBTSxpQkFBaUIsR0FBRztJQUN4QixVQUFVO0lBQ1YsT0FBTztJQUNQLE9BQU87SUFDUCxRQUFRO0lBQ1IsUUFBUTtJQUNSLFFBQVE7SUFDUixRQUFRO0lBQ1IsYUFBYTtDQUNMLENBQUM7QUFLWCxzR0FBc0c7QUFDdEcsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFFBQVEsRUFBRSxRQUFRLEVBQUUsUUFBUSxDQUFDLENBQUM7QUFDeEQsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLE9BQU8sRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDMUQsb0ZBQW9GO0FBQ3BGLE1BQU0sb0JBQW9CLEdBQUcsZUFBZSxDQUFDO0FBQzdDLE1BQU0seUJBQXlCLEdBQUcsaUNBQWlDLENBQUM7QUFDcEUsa0ZBQWtGO0FBQ2xGLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxDQUFDO0FBRTNCLHlFQUF5RTtBQUV6RSxpREFBaUQ7QUFDakQsU0FBUyxJQUFJLENBQUMsQ0FBcUI7SUFDakMsSUFBSSxDQUFDLElBQUksSUFBSTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ2hDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNuQixPQUFPLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2xDLENBQUM7QUFFRCxTQUFTLGdC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