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.
- package/lib/constants/product-constants.js +2 -2
- package/lib/factories/Order/OrderFromFormState.js +3 -3
- package/lib/helpers/OrderHelpers.d.ts +16 -0
- package/lib/helpers/OrderHelpers.js +65 -0
- package/lib/models/Order.d.ts +1 -0
- package/lib/models/Order.js +2 -0
- package/lib/types/rerobe-order-types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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;
|
package/lib/models/Order.d.ts
CHANGED
package/lib/models/Order.js
CHANGED
|
@@ -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,
|