Task 1-1: Idempotency Audit - Webhook Handlers
Phase: 1 Priority: High Module:
web,webmarketplaceDepends on: Không có Reference: docs/BountyHunter-Backend/details/feature-payment-marketplace/SPEC.md
Background
Webhook endpoints nhận confirm từ external gateways (Fincode, Slash, Apple, Google). Nếu gateway retry, hệ thống phải không double credit user.
Tasks
1. Audit tất cả webhook handlers
DI Note: Các handler trong
PaymentServicecần injectPaymentOrderRepository(hoặc tương đương) để query order status.UserSystemCoinService(hoặc tương đương) cần inject để credit coin. Đảm bảo cả 2 operations xảy ra trong cùng 1@Transactionalscope.
- [ ]
confirmCardPayment: check nếu order đãSUCCESS→ skip (idempotency guard) - [ ]
confirmCryptoPayment: verify Boolean return và idempotency — trace qua call chain để tìm nơi status check - [ ]
confirmNftTransfer(3 branches: ADMIN, OWNER, default): check if already confirmed cho từng branch - [ ]
mintCompressNftConfirm: check duplicate call và handleleafIndex=Unknown(log warn, không crash)
2. Chuẩn hóa pattern cho mọi webhook
// Pattern chuẩn (áp dụng cho mọi webhook handler):
@Transactional // Status update + coin credit trong cùng 1 transaction
public ResponseWebhookDto confirmCardPayment(Map<String, Object> params) {
String orderId = extractOrderId(params);
PaymentOrder order = orderRepository.findById(orderId)
.orElseThrow(() -> new BadRequestException("ORDER_NOT_FOUND"));
// Idempotency guard: re-check status trước khi process
if (order.getStatus() != PaymentStatus.PENDING) {
LOGGER.warn("[WEBHOOK] Order {} already processed: status={}", orderId, order.getStatus());
return ResponseWebhookDto.already_processed();
}
// Process in transaction:
order.setStatus(PaymentStatus.SUCCESS);
orderRepository.update(order);
userSystemCoinService.deposit(order.getUserId(), order.getCoinAmount());
return ResponseWebhookDto.ok();
}
3. Test signature validation
- [ ] Fincode webhook: validate
Fincode-Signatureheader — reject trước khi query DB - [ ] Slash webhook: validate signature theo Slash docs
- [ ] Apple/Google IAP: validate receipt/purchaseToken với Apple/Google server trước khi credit
Verification / Acceptance Criteria
- [ ] Tất cả 4 webhook handlers có idempotency guard (status check trước khi process)
- [ ] Gọi cùng webhook 2 lần → lần 2 trả
already_processedhoặc 200 không thay đổi state - [ ] Coin không bị credited 2 lần (verify balance unchanged sau duplicate webhook)
- [ ] Invalid signature → 4xx response trước khi bất kỳ DB operation nào
- [ ]
@Transactionalbao phủ cả status update lẫn coin credit
Files to Modify
web/controllers/coin/CoinController.java(confirmCardPayment, confirmCryptoPayment)webmarketplace/controllers/nft/NftController.java(confirmNftTransfer, mintCompressNftConfirm)application-core/service/payment/PaymentService.java(các confirm methods)