@mohasinac/appkit 4.42.15 → 4.42.17
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.
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* @tag sideEffects:firestore-write
|
|
23
23
|
*/
|
|
24
24
|
import { offerRepository } from "../../../../features/seller/repository/offer.repository";
|
|
25
|
+
import { cartRepository } from "../../../../features/cart/repository/cart.repository";
|
|
25
26
|
import { bidRepository } from "../../../../repositories";
|
|
26
27
|
import { OfferStatusValues } from "../../../../features/seller/schemas";
|
|
27
28
|
import { BID_COLLECTION } from "../../../../features/auctions/schemas/firestore";
|
|
@@ -199,7 +200,31 @@ export async function assertLockedLinesStillValid(items, buyerUid) {
|
|
|
199
200
|
continue;
|
|
200
201
|
const offer = await offerRepository.findById(item.offerId);
|
|
201
202
|
if (!offer) {
|
|
202
|
-
|
|
203
|
+
/*
|
|
204
|
+
* 🛑 PRUNE IT, do not just refuse. This used to throw "Remove it and
|
|
205
|
+
* try again" — an instruction the buyer cannot follow, because
|
|
206
|
+
* `cartRepository.removeItem` rejects any `locked` line and the cart
|
|
207
|
+
* page renders no remove control for one. Three guards then disagreed
|
|
208
|
+
* about one fact and trapped the buyer between them: checkout said
|
|
209
|
+
* remove it, remove said the line requires payment, and
|
|
210
|
+
* `assertCanAddNewItems` said settle the offer lane first — so the
|
|
211
|
+
* whole cart, including unrelated standard items, became unbuyable
|
|
212
|
+
* with no way out. Measured on production: three real items stranded
|
|
213
|
+
* behind one tombstone line.
|
|
214
|
+
*
|
|
215
|
+
* At THIS point the lock protects nothing. The lock exists so a buyer
|
|
216
|
+
* cannot walk away from a committed purchase; an offer record that no
|
|
217
|
+
* longer exists is not a commitment to anything, and the line can never
|
|
218
|
+
* be fulfilled. `removeItemsByOfferId` is the same path admin
|
|
219
|
+
* cancellation already uses, so it is allowed to clear a locked line.
|
|
220
|
+
*
|
|
221
|
+
* Still a ValidationError rather than a silent continue: the buyer's
|
|
222
|
+
* total just changed, and a checkout that quietly drops a line and
|
|
223
|
+
* charges a different amount than the screen showed is its own defect.
|
|
224
|
+
* They retry once and it goes through.
|
|
225
|
+
*/
|
|
226
|
+
await cartRepository.removeItemsByOfferId(buyerUid, item.offerId);
|
|
227
|
+
throw new ValidationError("One item was removed because its offer no longer exists. Please review your cart and try again.");
|
|
203
228
|
}
|
|
204
229
|
if (offer.buyerUid !== buyerUid) {
|
|
205
230
|
throw new ValidationError("This offer belongs to a different account.");
|
|
@@ -21,6 +21,7 @@ import { normalizeError } from "../../../../errors/normalize";
|
|
|
21
21
|
import { serverLogger } from "../../../../monitoring";
|
|
22
22
|
import { ValidationError } from "../../../../errors";
|
|
23
23
|
import { ORDER_FIELDS } from "../../../../constants/field-names";
|
|
24
|
+
import { firestoreTimestampToDate } from "../../../../utils/type.converter";
|
|
24
25
|
import { PRODUCT_COLLECTION, PRODUCT_CODES_SUBCOLLECTION, } from "../../../../features/products/schemas/firestore";
|
|
25
26
|
/**
|
|
26
27
|
* Where a delivered asset's bytes live.
|
|
@@ -181,8 +182,28 @@ export async function listPoolEntries(productId) {
|
|
|
181
182
|
status: c.status,
|
|
182
183
|
...(c.fileName ? { fileName: c.fileName } : {}),
|
|
183
184
|
...(c.orderId ? { orderId: c.orderId } : {}),
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
/*
|
|
186
|
+
* 🛑 firestoreTimestampToDate, never `new Date(value)`.
|
|
187
|
+
*
|
|
188
|
+
* addPoolEntries writes `createdAt: new Date()`, and the Admin SDK stores
|
|
189
|
+
* that as a Firestore Timestamp and reads it back as one — NOT as a Date.
|
|
190
|
+
* `new Date(aTimestamp)` is an Invalid Date, and `.toISOString()` on an
|
|
191
|
+
* Invalid Date THROWS RangeError, so this map took the whole route down
|
|
192
|
+
* with a 500.
|
|
193
|
+
*
|
|
194
|
+
* It hid for as long as it possibly could: mapping over ZERO documents
|
|
195
|
+
* never throws, so GET answered `[]` for the entire period the pool had
|
|
196
|
+
* no writer (Root Cause #103) and only began failing once the first entry
|
|
197
|
+
* existed. The seller then saw "Nothing in the pool yet" — the component
|
|
198
|
+
* rendering a failed load as an empty state — and the rational response
|
|
199
|
+
* is to add the codes again, which silently accumulates duplicates.
|
|
200
|
+
*/
|
|
201
|
+
...(c.claimedAt
|
|
202
|
+
? { claimedAt: firestoreTimestampToDate(c.claimedAt).toISOString() }
|
|
203
|
+
: {}),
|
|
204
|
+
...(c.createdAt
|
|
205
|
+
? { createdAt: firestoreTimestampToDate(c.createdAt).toISOString() }
|
|
206
|
+
: {}),
|
|
186
207
|
};
|
|
187
208
|
});
|
|
188
209
|
}
|