rerobe-js-orm 4.4.0 → 4.4.2

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.
@@ -5,7 +5,7 @@ const Order_1 = require("../../models/Order");
5
5
  const order_constants_1 = require("../../constants/order-constants");
6
6
  class OrderFromFormState extends OrderFactory_1.default {
7
7
  createOrder(fs) {
8
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
8
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7;
9
9
  const currencyCode = fs.fields.currencyCode.inputValue || 'SEK';
10
10
  const customer = fs.fields.customer.selectedValue || {};
11
11
  const customerAttr = {
@@ -98,7 +98,7 @@ class OrderFromFormState extends OrderFactory_1.default {
98
98
  fulfillmentStatus: fs.fields.fulfillmentStatus.selectedValue,
99
99
  statusUrl: ((_j = fs.props) === null || _j === void 0 ? void 0 : _j.statusUrl) || '',
100
100
  };
101
- const orderAttributes = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, totalPricesAttr), presentmentTotalPricesAttr), discountsAttr), shippingAndLocationAttr), customerAttr), statusesAttr), { id: ((_k = fs.props) === null || _k === void 0 ? void 0 : _k.id) || '', documentId: ((_l = fs.props) === null || _l === void 0 ? void 0 : _l.documentId) || '', name: ((_m = fs.props) === null || _m === void 0 ? void 0 : _m.name) || '', orderNumber: (_o = fs.props) === null || _o === void 0 ? void 0 : _o.orderNumber, ribbnOrderNumber: (_p = fs.props) === null || _p === void 0 ? void 0 : _p.ribbnOrderNumber, merchantId: ((_q = fs.props) === null || _q === void 0 ? void 0 : _q.merchantId) || '', currencyCode: fs.fields.currencyCode.inputValue, lineItems: fs.fields.lineItems.selectedValues || [], fulfillments: ((_r = fs.props) === null || _r === void 0 ? void 0 : _r.fulfillments) || [], refunds: ((_s = fs.props) === null || _s === void 0 ? void 0 : _s.refunds) || [], paymentType: fs.fields.paymentType.selectedValue, paymentMethod: fs.fields.paymentMethod.selectedValue || '', paymentDetails: fs.fields.paymentDetails.selectedValue || {}, notes: fs.fields.notes.inputValue || '', tags: fs.fields.tags.selectedValues || [], cancelReason: fs.fields.cancelReason.selectedValue || '', canceledAt: ((_t = fs.props) === null || _t === void 0 ? void 0 : _t.canceledAt) || '', processedAt: ((_u = fs.props) === null || _u === void 0 ? void 0 : _u.processedAt) || new Date().toUTCString(), adjustments: ((_v = fs.props) === null || _v === void 0 ? void 0 : _v.adjustments) || [], createdAtTimestamp: 0, updatedAtTimestamp: 0, createdByUserId: ((_w = fs.props) === null || _w === void 0 ? void 0 : _w.createdByUserId) || '', taxTitle: fs.fields.taxTitle.inputValue || '' }), (((_x = fs.props) === null || _x === void 0 ? void 0 : _x.zettleTransactionObj) && {
101
+ const orderAttributes = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, totalPricesAttr), presentmentTotalPricesAttr), discountsAttr), shippingAndLocationAttr), customerAttr), statusesAttr), { id: ((_k = fs.props) === null || _k === void 0 ? void 0 : _k.id) || '', documentId: ((_l = fs.props) === null || _l === void 0 ? void 0 : _l.documentId) || '', name: ((_m = fs.props) === null || _m === void 0 ? void 0 : _m.name) || '', orderNumber: (_o = fs.props) === null || _o === void 0 ? void 0 : _o.orderNumber, ribbnOrderNumber: (_p = fs.props) === null || _p === void 0 ? void 0 : _p.ribbnOrderNumber, merchantId: ((_q = fs.props) === null || _q === void 0 ? void 0 : _q.merchantId) || '', currencyCode: fs.fields.currencyCode.inputValue, lineItems: fs.fields.lineItems.selectedValues || [], fulfillments: ((_r = fs.props) === null || _r === void 0 ? void 0 : _r.fulfillments) || [], refunds: ((_s = fs.props) === null || _s === void 0 ? void 0 : _s.refunds) || [], paymentType: fs.fields.paymentType.selectedValue, paymentMethod: fs.fields.paymentMethod.selectedValue || '', paymentDetails: fs.fields.paymentDetails.selectedValue || {}, notes: fs.fields.notes.inputValue || '', tags: fs.fields.tags.selectedValues || [], cancelReason: fs.fields.cancelReason.selectedValue || '', canceledAt: ((_t = fs.props) === null || _t === void 0 ? void 0 : _t.canceledAt) || '', processedAt: ((_u = fs.props) === null || _u === void 0 ? void 0 : _u.processedAt) || new Date().toUTCString(), adjustments: ((_v = fs.props) === null || _v === void 0 ? void 0 : _v.adjustments) || [], createdAtTimestamp: 0, updatedAtTimestamp: 0, createdByUserId: ((_w = fs.props) === null || _w === void 0 ? void 0 : _w.createdByUserId) || '', taxTitle: fs.fields.taxTitle.inputValue || '' }), (((_x = fs.props) === null || _x === void 0 ? void 0 : _x.zettleTransactionObj) && {
102
102
  zettleTransactionObj: (_y = fs.props) === null || _y === void 0 ? void 0 : _y.zettleTransactionObj,
103
103
  })), (((_z = fs.props) === null || _z === void 0 ? void 0 : _z.paymentIntentObj) && {
104
104
  paymentIntentObj: (_0 = fs.props) === null || _0 === void 0 ? void 0 : _0.paymentIntentObj,
@@ -108,7 +108,7 @@ class OrderFromFormState extends OrderFactory_1.default {
108
108
  receiptLog: (_4 = fs.props) === null || _4 === void 0 ? void 0 : _4.receiptLog,
109
109
  })), (((_5 = fs.props) === null || _5 === void 0 ? void 0 : _5.channelPartnerOrderId) && {
110
110
  channelPartnerOrderId: (_6 = fs.props) === null || _6 === void 0 ? void 0 : _6.channelPartnerOrderId,
111
- }));
111
+ })), { idempotencyKey: ((_7 = fs.props) === null || _7 === void 0 ? void 0 : _7.idempotencyKey) || '' });
112
112
  return new Order_1.default(Object.assign({}, orderAttributes));
113
113
  }
114
114
  }
@@ -178,6 +178,7 @@ export const productYesNoOptions: {
178
178
  export const productClassOptions: {
179
179
  label: string;
180
180
  value: string;
181
+ description: string;
181
182
  }[];
182
183
  export const productOptions: {
183
184
  value: string;
@@ -1060,16 +1060,19 @@ const productYesNoOptions = [
1060
1060
  exports.productYesNoOptions = productYesNoOptions;
1061
1061
  const productClassOptions = [
1062
1062
  {
1063
- label: 'Unique (One-of-a-kind, no variants)',
1063
+ label: 'Unique',
1064
1064
  value: 'UNIQUE',
1065
+ description: 'One-of-a-kind, no variants',
1065
1066
  },
1066
1067
  {
1067
- label: 'Multi (Parent with multiple variants)',
1068
+ label: 'Multiple',
1068
1069
  value: 'MULTI',
1070
+ description: 'Many duplicates of the same product (e.g., gift cards, shopping bags, etc.)',
1069
1071
  },
1070
1072
  {
1071
- label: 'Template (Blueprint for cloning)',
1073
+ label: 'Template',
1072
1074
  value: 'TEMPLATE',
1075
+ description: 'Blueprint for cloning a variant of the product at checkout',
1073
1076
  },
1074
1077
  ];
1075
1078
  exports.productClassOptions = productClassOptions;
@@ -54,4 +54,20 @@ export default class OrderHelpers {
54
54
  receiptNumber: string;
55
55
  additionalInfo: string;
56
56
  }): ReceiptObj;
57
+ /**
58
+ * Generates an idempotency key from a ReRobeOrderObj to prevent duplicate order creation
59
+ * Uses order content (lineItems, email, amount) plus a time bucket to allow legitimate
60
+ * reorders while preventing rapid duplicates within a short time window
61
+ */
62
+ generateIdempotencyKey(order: ReRobeOrderObj, timeBucketMinutes?: number): string;
63
+ /**
64
+ * Generates a time bucket string for idempotency key
65
+ * Orders within the same time bucket will have the same key component
66
+ */
67
+ private generateTimeBucket;
68
+ /**
69
+ * Generates a hash of line items to include in idempotency key
70
+ * Focuses on stable product identifiers and quantities
71
+ */
72
+ private generateLineItemsHash;
57
73
  }
@@ -4,6 +4,7 @@ const Order_1 = require("../models/Order");
4
4
  const Utilities_1 = require("../helpers/Utilities");
5
5
  const order_constants_1 = require("../constants/order-constants");
6
6
  const translations_1 = require("../translations");
7
+ const crypto_1 = require("crypto");
7
8
  class OrderHelpers {
8
9
  getOrderIdFromShopifyObj(order) {
9
10
  const { id, tags } = order;
@@ -691,5 +692,69 @@ class OrderHelpers {
691
692
  fractionDigits: 2,
692
693
  }), taxRates: formattedTaxRates }, paymentTerminalInfo), receiptLabels);
693
694
  }
695
+ /**
696
+ * Generates an idempotency key from a ReRobeOrderObj to prevent duplicate order creation
697
+ * Uses order content (lineItems, email, amount) plus a time bucket to allow legitimate
698
+ * reorders while preventing rapid duplicates within a short time window
699
+ */
700
+ generateIdempotencyKey(order, timeBucketMinutes = 5) {
701
+ var _a;
702
+ // Financial fingerprint - core of the order
703
+ const totalAmount = ((_a = order.totalPrice) === null || _a === void 0 ? void 0 : _a.amount) || '0';
704
+ const currencyCode = order.currencyCode || '';
705
+ // Customer identifier - who is placing the order
706
+ const customerEmail = order.email || '';
707
+ // Merchant scope - prevent cross-merchant collisions
708
+ const merchantId = order.merchantId || '';
709
+ // Time bucket - allows same order after time window but prevents rapid duplicates
710
+ const timeBucket = this.generateTimeBucket(order.createdAtTimestamp || Date.now(), timeBucketMinutes);
711
+ // Create a hash of line items - what is being ordered
712
+ const lineItemsHash = this.generateLineItemsHash(order.lineItems || []);
713
+ // Combine all fields into a consistent string
714
+ // Note: We explicitly exclude client-generated IDs (id, shopifyId, orderNumber)
715
+ // since clients control these and could bypass idempotency protection
716
+ const keyComponents = [customerEmail, totalAmount, currencyCode, merchantId, timeBucket, lineItemsHash];
717
+ // Filter out empty values and join
718
+ const keyString = keyComponents.filter((component) => component && String(component).trim().length > 0).join('|');
719
+ // Generate SHA-256 hash for consistent length and uniqueness
720
+ return (0, crypto_1.createHash)('sha256').update(keyString).digest('hex').substring(0, 32); // Truncate to 32 characters for manageable length
721
+ }
722
+ /**
723
+ * Generates a time bucket string for idempotency key
724
+ * Orders within the same time bucket will have the same key component
725
+ */
726
+ generateTimeBucket(timestamp, bucketMinutes) {
727
+ const bucketSizeMs = bucketMinutes * 60 * 1000;
728
+ const bucketStart = Math.floor(timestamp / bucketSizeMs) * bucketSizeMs;
729
+ return bucketStart.toString();
730
+ }
731
+ /**
732
+ * Generates a hash of line items to include in idempotency key
733
+ * Focuses on stable product identifiers and quantities
734
+ */
735
+ generateLineItemsHash(lineItems) {
736
+ if (!lineItems || lineItems.length === 0) {
737
+ return '';
738
+ }
739
+ // Create a normalized representation of line items
740
+ const normalizedItems = lineItems
741
+ .map((item) => {
742
+ var _a, _b;
743
+ return {
744
+ productId: item.productId || '',
745
+ shopifyProductId: item.shopifyProductId || '',
746
+ variantId: ((_a = item.variant) === null || _a === void 0 ? void 0 : _a.id) || '',
747
+ quantity: item.quantity || 0,
748
+ // Include price to detect any price changes
749
+ unitPrice: ((_b = item.originalUnitPrice) === null || _b === void 0 ? void 0 : _b.amount) || '0',
750
+ };
751
+ })
752
+ .sort((a, b) => {
753
+ // Sort by productId to ensure consistent ordering
754
+ return (a.productId || '').localeCompare(b.productId || '');
755
+ });
756
+ const itemsString = JSON.stringify(normalizedItems);
757
+ return (0, crypto_1.createHash)('md5').update(itemsString).digest('hex').substring(0, 16); // Truncate MD5 to 16 characters
758
+ }
694
759
  }
695
760
  exports.default = OrderHelpers;
@@ -754,6 +754,7 @@ class ReRobeProductHelpers {
754
754
  };
755
755
  }
756
756
  static convertToMetaCommerceProduct(productObj, merchantObj, targetMarketTaxRate = 0) {
757
+ var _a;
757
758
  const { primaryDomain, currency } = merchantObj;
758
759
  const availabilityMapper = (status) => {
759
760
  if (!status)
@@ -845,6 +846,9 @@ class ReRobeProductHelpers {
845
846
  size: productObj.standardSize || productObj.size || 'unknown',
846
847
  material: this.materialCompJoinedString(productObj.materialComposition),
847
848
  age_group: ageGroupMapper(productObj.primaryAgeCategory),
849
+ item_group_id: Array.isArray(productObj.tags)
850
+ ? ((_a = productObj.tags.find((tag) => tag === null || tag === void 0 ? void 0 : tag.startsWith('item_group_id:'))) === null || _a === void 0 ? void 0 : _a.split(':')[1]) || ''
851
+ : '',
848
852
  };
849
853
  }
850
854
  }
@@ -47,6 +47,7 @@ export default class Order extends Base {
47
47
  id: string;
48
48
  appliedDiscount: AppliedDiscount;
49
49
  documentId: string;
50
+ idempotencyKey: string;
50
51
  name: string;
51
52
  orderNumber: number;
52
53
  currencyCode: string;
@@ -10,6 +10,7 @@ class Order extends Base_1.default {
10
10
  amount: 0,
11
11
  };
12
12
  this.documentId = (props === null || props === void 0 ? void 0 : props.documentId) || '';
13
+ this.idempotencyKey = (props === null || props === void 0 ? void 0 : props.idempotencyKey) || '';
13
14
  this.name = (props === null || props === void 0 ? void 0 : props.name) || '';
14
15
  this.orderNumber = (props === null || props === void 0 ? void 0 : props.orderNumber) || 0;
15
16
  this.currencyCode = (props === null || props === void 0 ? void 0 : props.currencyCode) || 'SEK';
@@ -149,6 +150,7 @@ class Order extends Base_1.default {
149
150
  const orderObj = {
150
151
  id: this.id,
151
152
  documentId: this.documentId,
153
+ idempotencyKey: this.idempotencyKey,
152
154
  appliedDiscount: this.appliedDiscount,
153
155
  name: this.name,
154
156
  orderNumber: this.orderNumber,
@@ -87,6 +87,7 @@ type ReRobeOrderObj = {
87
87
  saleDiscount?: Money;
88
88
  additionalDiscount?: Money;
89
89
  documentId?: string;
90
+ idempotencyKey?: string;
90
91
  name: string;
91
92
  orderNumber: number;
92
93
  currencyCode: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rerobe-js-orm",
3
- "version": "4.4.0",
3
+ "version": "4.4.2",
4
4
  "description": "ReRobe's Javascript ORM Framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",