| Line 319... |
Line 319... |
| 319 |
}
|
319 |
}
|
| 320 |
|
320 |
|
| 321 |
public void createScratchOffer(FofoOrder order, OfferEntity offer, GiftEntity gift) {
|
321 |
public void createScratchOffer(FofoOrder order, OfferEntity offer, GiftEntity gift) {
|
| 322 |
int updated = giftRepository.decrementMaxRedemptions(gift.getId());
|
322 |
int updated = giftRepository.decrementMaxRedemptions(gift.getId());
|
| 323 |
if (updated == 0) {
|
323 |
if (updated == 0) {
|
| - |
|
324 |
// Race: another concurrent scratch drained this gift's stock to 0 between
|
| - |
|
325 |
// our eligibility read and our atomic decrement. We MUST still give the
|
| - |
|
326 |
// customer a scratch record — either via the Neckband safety-net path or
|
| - |
|
327 |
// (worst case) BLNT. Silent return here caused the "no scratch created"
|
| - |
|
328 |
// bug that silently ate ~12+ orders on 2026-08-13.
|
| - |
|
329 |
logger.warn("Gift {} decrement raced (stock hit 0); falling back to Neckband safety-net for order {}",
|
| - |
|
330 |
gift.getId(), order.getId());
|
| - |
|
331 |
try {
|
| - |
|
332 |
GiftEntity defaultGift = giftRepository.getDefaultGift(offer.getId());
|
| - |
|
333 |
float totalAmount = order.getTotalAmount();
|
| - |
|
334 |
Set<Integer> itemCategoryIds = new HashSet<>();
|
| - |
|
335 |
List<FofoOrderItem> orderItems = fofoOrderItemRepository.selectByOrderId(order.getId());
|
| - |
|
336 |
for (FofoOrderItem oi : orderItems) {
|
| - |
|
337 |
try {
|
| - |
|
338 |
Item it = itemRepository.selectById(oi.getItemId());
|
| 324 |
logger.warn("Gift {} has no remaining redemptions", gift.getId());
|
339 |
if (it != null) itemCategoryIds.add(it.getCategoryId());
|
| - |
|
340 |
} catch (Exception ignore) {
|
| - |
|
341 |
}
|
| - |
|
342 |
}
|
| - |
|
343 |
awardDefaultOrBlnt(order, offer, defaultGift, totalAmount, itemCategoryIds,
|
| - |
|
344 |
"createScratchOffer-decrement-race");
|
| - |
|
345 |
} catch (Exception e) {
|
| - |
|
346 |
logger.error("Fallback after decrement race also failed for order {} — awarding BLNT",
|
| - |
|
347 |
order.getId(), e);
|
| - |
|
348 |
createBetterLuckNextTimeScratch(order, offer);
|
| - |
|
349 |
}
|
| 325 |
return;
|
350 |
return;
|
| 326 |
}
|
351 |
}
|
| 327 |
|
352 |
|
| 328 |
ScratchOffer so = new ScratchOffer();
|
353 |
ScratchOffer so = new ScratchOffer();
|
| 329 |
so.setCustomerId(order.getCustomerId());
|
354 |
so.setCustomerId(order.getCustomerId());
|