rerobe-js-orm 4.9.16 → 4.9.18
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,6 +5,7 @@ export default class RefundFormState extends FormState {
|
|
|
5
5
|
fields: OrderRefundFormFields;
|
|
6
6
|
props: CompleteRefund;
|
|
7
7
|
orderHelpers: OrderHelpers;
|
|
8
|
+
totalRefundedManuallyEdited: boolean;
|
|
8
9
|
constructor(props?: any);
|
|
9
10
|
createRefund(): Refund;
|
|
10
11
|
updateTotalRefunded(): void;
|
|
@@ -8,6 +8,11 @@ class RefundFormState extends FormState_1.default {
|
|
|
8
8
|
constructor(props) {
|
|
9
9
|
super();
|
|
10
10
|
this.orderHelpers = new OrderHelpers_1.default();
|
|
11
|
+
// Once an admin types an explicit refund amount, that value is authoritative.
|
|
12
|
+
// Line-item / shipping edits must NOT silently recompute (and thereby wipe) it,
|
|
13
|
+
// because the server-side order trigger issues a REAL Stripe refund for
|
|
14
|
+
// `refund.totalRefunded.amount` — a clobbered value over-refunds the customer.
|
|
15
|
+
this.totalRefundedManuallyEdited = false;
|
|
11
16
|
this.props = Object.assign(Object.assign({}, new Refund_1.default(props).toObj()), { orderMoreInfo: (props === null || props === void 0 ? void 0 : props.order) ? this.orderHelpers.getOrderInfoWithRefundsAndFulfillments(props === null || props === void 0 ? void 0 : props.order) : {} });
|
|
12
17
|
this.fields.totalRefunded = this.fieldFactory('textInput', 'totalRefunded');
|
|
13
18
|
this.fields.subtotalRefunded = this.fieldFactory('textInput', 'subtotalRefunded');
|
|
@@ -31,12 +36,17 @@ class RefundFormState extends FormState_1.default {
|
|
|
31
36
|
return refundFactory.createRefund(this);
|
|
32
37
|
}
|
|
33
38
|
updateTotalRefunded() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Number(this.fields.
|
|
39
|
-
|
|
39
|
+
// Only auto-derive the refund total from line items when the admin has NOT
|
|
40
|
+
// typed an explicit amount. Otherwise their entered value (e.g. full amount
|
|
41
|
+
// minus a withheld return fee) is preserved.
|
|
42
|
+
if (!this.totalRefundedManuallyEdited) {
|
|
43
|
+
this.fields.totalRefunded.inputValue = this.orderHelpers.toFixedPointPrice(Number(this.fields.subtotalRefunded.inputValue) +
|
|
44
|
+
Number(this.fields.refundShipping.inputValue) +
|
|
45
|
+
Number(this.fields.totalTax.inputValue));
|
|
46
|
+
this.fields.totalRefundedPresentment.inputValue = this.orderHelpers.toFixedPointPrice(Number(this.fields.subtotalRefundedPresentment.inputValue) +
|
|
47
|
+
Number(this.fields.refundShippingPresentment.inputValue) +
|
|
48
|
+
Number(this.fields.totalTaxPresentment.inputValue));
|
|
49
|
+
}
|
|
40
50
|
this.fields.refundCurrency.inputValue =
|
|
41
51
|
Number(this.fields.totalRefunded.inputValue) - Number(this.fields.refundCreditDiscount.inputValue);
|
|
42
52
|
this.fields.refundCurrencyPresentment.inputValue =
|
|
@@ -219,6 +229,12 @@ class RefundFormState extends FormState_1.default {
|
|
|
219
229
|
fieldKey === 'totalRefundedPresentment') {
|
|
220
230
|
onChangeHandler = (val) => {
|
|
221
231
|
this.fields[fieldKey].inputValue = String(val);
|
|
232
|
+
// Typing an explicit refund total marks it authoritative so later
|
|
233
|
+
// line-item / shipping edits can't recompute it away. Clearing the
|
|
234
|
+
// field (empty) re-enables auto-calculation from line items.
|
|
235
|
+
if (fieldKey === 'totalRefunded' || fieldKey === 'totalRefundedPresentment') {
|
|
236
|
+
this.totalRefundedManuallyEdited = String(val !== null && val !== void 0 ? val : '').trim() !== '';
|
|
237
|
+
}
|
|
222
238
|
this.updateRelatedField(fieldKey, Number(val || 0));
|
|
223
239
|
if (fieldKey === 'refundShipping' || fieldKey === 'refundShippingPresentment') {
|
|
224
240
|
this.updateTotalRefunded();
|
|
@@ -828,10 +828,28 @@ class OrderHelpers {
|
|
|
828
828
|
const timeBucket = this.generateTimeBucket(order.createdAtTimestamp || Date.now(), timeBucketMinutes);
|
|
829
829
|
// Create a hash of line items - what is being ordered
|
|
830
830
|
const lineItemsHash = this.generateLineItemsHash(order.lineItems || []);
|
|
831
|
+
// Payment anchor - the Stripe-issued PaymentIntent id, present once the order
|
|
832
|
+
// is paid. Including it gives two genuinely-distinct sales of an IDENTICAL
|
|
833
|
+
// cart different keys (different PaymentIntents), so a rapid, legitimate
|
|
834
|
+
// re-sale of the same stocked SKU is not mistaken for a duplicate — while
|
|
835
|
+
// retries of the SAME payment still collapse to one key. Omitted (filtered out
|
|
836
|
+
// below) when absent, so unpaid / offline orders keep the pure content + time
|
|
837
|
+
// fingerprint and behave exactly as before. Unlike id / shopifyId /
|
|
838
|
+
// orderNumber, the PaymentIntent id is Stripe-issued (not a client-forgeable
|
|
839
|
+
// arbitrary value), so it cannot be used to bypass idempotency.
|
|
840
|
+
const paymentIntentId = order.paymentIntentId || '';
|
|
831
841
|
// Combine all fields into a consistent string
|
|
832
842
|
// Note: We explicitly exclude client-generated IDs (id, shopifyId, orderNumber)
|
|
833
843
|
// since clients control these and could bypass idempotency protection
|
|
834
|
-
const keyComponents = [
|
|
844
|
+
const keyComponents = [
|
|
845
|
+
customerEmail,
|
|
846
|
+
totalAmount,
|
|
847
|
+
currencyCode,
|
|
848
|
+
merchantId,
|
|
849
|
+
timeBucket,
|
|
850
|
+
lineItemsHash,
|
|
851
|
+
paymentIntentId,
|
|
852
|
+
];
|
|
835
853
|
// Filter out empty values and join
|
|
836
854
|
const keyString = keyComponents.filter((component) => component && String(component).trim().length > 0).join('|');
|
|
837
855
|
// Generate SHA-256 hash for consistent length and uniqueness
|