rerobe-js-orm 4.4.1 → 4.4.3

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.
@@ -117,7 +117,7 @@ exports.PRODUCT_STATE_LABELS = {
117
117
  sellerDonated: 'Donated by you',
118
118
  merchantDonated: 'Donated by merchant',
119
119
  soldSellerSelfPay: 'Sold-awaiting self payout',
120
- soldSellerPayoutProcessing: 'Sold-payout processing',
120
+ soldSellerPayoutProcessing: 'Sold–Cashout available soon',
121
121
  soldSellerPayoutFailed: 'Sold-payout failed',
122
122
  hidden: 'Hidden',
123
123
  booked: 'Booked',
@@ -154,7 +154,7 @@ exports.PRODUCT_STATE_LABELS_ADMIN_VIEW = {
154
154
  sellerDonated: 'Donated by seller',
155
155
  merchantDonated: 'Donated by merchant',
156
156
  soldSellerSelfPay: 'Sold—awaiting seller self payout',
157
- soldSellerPayoutProcessing: 'Sold—seller payout processing',
157
+ soldSellerPayoutProcessing: 'Sold–Cashout available soon',
158
158
  soldSellerPayoutFailed: 'Sold—seller payout failed',
159
159
  hidden: 'Hidden',
160
160
  booked: 'Booked',
@@ -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
  }
@@ -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;
@@ -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.1",
3
+ "version": "4.4.3",
4
4
  "description": "ReRobe's Javascript ORM Framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",