-
Notifications
You must be signed in to change notification settings - Fork 150
Description
Payment Duplicate Confirmation Returns Success
Summary
Duplicate payment confirmation requests return HTTP 200/302 (success) instead of HTTP 409 (Conflict) or 422 (Unprocessable Entity). This breaks idempotency and prevents proper error handling in automated systems.
Location
File: /app/eventyay/base/models/orders.py
Method: OrderPayment.confirm()
Lines: 1668-1677
Current Behavior
def confirm(self, ...):
with transaction.atomic():
locked_instance = OrderPayment.objects.select_for_update().get(pk=self.pk)
if locked_instance.state == self.PAYMENT_STATE_CONFIRMED:
logger.info('Confirmed payment {} but ignored due to likely race condition.'.format(self.full_id))
return # Returns None - appears as success to callerWhen a payment is already confirmed:
- Method returns
Nonesilently - HTTP response: 200 OK or 302 Found (success)
- Log message: "race condition" written but no exception raised
- Client cannot distinguish success from duplicate
Expected Behavior
def confirm(self, ...):
with transaction.atomic():
locked_instance = OrderPayment.objects.select_for_update().get(pk=self.pk)
if locked_instance.state == self.PAYMENT_STATE_CONFIRMED:
raise PaymentAlreadyConfirmedException(
f'Payment {self.full_id} has already been confirmed.'
)Should return:
- HTTP 409 Conflict or 422 Unprocessable Entity
- Error response body with clear message
- Exception that can be caught and handled
Reproduction Steps
- Navigate to order with pending payment:
http://localhost:8000/control/event/aer/xgzvvq/orders/90DEK/ - Click "Mark as paid" - payment confirmed successfully
- Open browser console and run:
fetch('/control/event/aer/xgzvvq/orders/90DEK/payments/3/confirm', {
method: 'POST',
headers: {'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value},
credentials: 'include'
}).then(r => console.log('Status:', r.status))- Observe: Returns 200 OK instead of 409 Conflict
Evidence
Console output:
Status: 200 - Expected: 409, Actual: BUG
Server logs:
INFO 2025-11-24 17:05:20,410 runserver: HTTP POST /control/event/aer/xgzvvq/orders/90DEK/payments/3/confirm 302
INFO 2025-11-24 17:05:49,745 runserver: HTTP POST /control/event/aer/xgzvvq/orders/90DEK/payments/3/confirm 302
Both requests return 302 (success redirect). No error returned to client.
Impact: Critical Use Cases
1. Payment Gateway Webhooks
Scenario: Stripe/PayPal sends duplicate webhook due to network retry or timeout.
@webhook_handler('/stripe/webhook')
def process_stripe_webhook(payload):
payment_id = payload['payment_intent']
payment = OrderPayment.objects.get(provider_id=payment_id)
result = payment.confirm() # Returns None for both first and duplicate
if result is None: # TRUE for both requests!
send_ticket_email(payment.order) # Sent twice
credit_user_account(payment.order.user, payment.amount) # Credited twice
trigger_fulfillment(payment.order) # Processed twiceImpact: Duplicate tickets sent, double account credits, duplicate order processing.
2. API Client Retry Logic
Scenario: Mobile app or external integration implements retry on network failure.
async function confirmPayment(paymentId) {
try {
const response = await api.post(`/payments/${paymentId}/confirm`);
if (response.ok) {
return {success: true};
}
} catch (error) {
// Network timeout - retry
const retry = await api.post(`/payments/${paymentId}/confirm`);
if (retry.ok) { // Also returns 200!
return {success: true}; // Can't detect duplicate
}
}
}Impact: Cannot implement safe retry logic. Cannot distinguish network failure from duplicate.
3. Background Job Idempotency
Scenario: Scheduled task processes pending payments, runs twice due to deployment or system error.
# Cron job: Process bank transfers
def process_bank_transfers():
for payment in get_received_bank_transfers():
result = payment.confirm()
# result is None for both first run and duplicate run
metrics.increment('payments.confirmed') # Counted twice
send_notification(payment.order.user) # Sent twiceImpact: Incorrect metrics, duplicate notifications, impossible to detect job ran twice.
4. Concurrent Admin Actions
Scenario: Two administrators or admin + automated system confirm same payment simultaneously.
Time 10:00:00.500 - Admin A clicks "Confirm payment"
Time 10:00:00.502 - Admin B clicks "Confirm payment"
Both receive 200 OK response
Neither knows the other just confirmed it
Audit logs show both succeeded
Impact: Confusion in audit logs, no clear indication of duplicate action, compliance issues.
5. Load Balancer Request Duplication
Scenario: Load balancer retries request on backend timeout.
Client -> Load Balancer -> Backend Server A (processing, slow response)
Load Balancer timeout (30s) -> Retry to Backend Server B
Both servers process the request
Both return 200 OK
Impact: Silent duplicate processing at infrastructure level, impossible to detect or prevent.
Why This Matters
Broken Idempotency Contract
REST APIs should be idempotent: same request multiple times = same result with clear indication.
Current broken contract:
- First request: 200 OK, payment confirmed
- Second request: 200 OK, payment already confirmed (but looks identical)
- Client cannot distinguish the two
Correct idempotent contract:
- First request: 200 OK, payment confirmed
- Second request: 409 Conflict, already confirmed (clearly different)
- Client can handle each appropriately
No Error Signaling
Systems depend on HTTP status codes for automated decision-making:
- 2xx = Success, proceed
- 4xx = Client error, don't retry
- 5xx = Server error, retry later
Current behavior breaks this by returning 2xx for a client error (duplicate confirmation).
Monitoring Blindness
Monitoring systems cannot detect issues:
- No errors logged at ERROR level
- No metrics for duplicate confirmations
- No alerts triggered
- Silent failures in production
Data Integrity Risk
Without proper error handling:
- Financial reconciliation issues (duplicate credits)
- Inventory management issues (quota counting errors)
- Customer support issues (duplicate tickets/emails)
- Compliance issues (audit log ambiguity)
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
