fix: terminal Codex review — duplicate payments, overcharges, double dispatch, cart flow#57
Merged
JacobCoffee merged 2 commits intomainfrom Mar 19, 2026
Merged
Conversation
…tch in Terminal backend Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oads - Checkout cart into order before creating payment intent (proper line items) - Pass attendee_access_code to checkout and walk-up intent payloads - Replace misleading voucher stub with clear "not supported" message Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the Stripe Terminal POS flow to prevent duplicate payments/overcharges, align reader processing with the Stripe Terminal JS SDK, and ensure walk-up cart sales create proper Orders before charging.
Changes:
- Prevent duplicate
Paymentcreation on repeatedpayment_intent.succeededwebhooks for already-succeeded intents. - Tighten terminal intent creation to require PENDING orders and charge only the remaining balance; improve JSON parsing and cancel flow to rely on stored
reader_id. - Update terminal POS UI to checkout cart → order before charging, disable voucher application, and pass attendee ownership via
attendee_access_code.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
src/django_program/registration/webhooks.py |
Skips creating duplicate succeeded payments for the same intent/order. |
src/django_program/registration/views_terminal.py |
Validates order state/balance, improves JSON parsing, adjusts order ownership for walk-ups, and uses stored reader IDs on cancel. |
src/django_program/manage/templates/django_program/manage/terminal_pos.html |
Ensures cart checkout occurs before charging; disables voucher usage; passes attendee access code. |
pyproject.toml |
Adds checkin to codespell ignore list. |
Comments suppressed due to low confidence (1)
src/django_program/registration/views_terminal.py:170
except InvalidOperation, TypeError:is invalid Python 3 syntax and will prevent the module from importing. Use tuple form (e.g.,except (InvalidOperation, TypeError):) to correctly handle Decimal parsing errors.
try:
amount = Decimal(str(raw_amount))
if amount <= 0:
return JsonResponse({"error": "Amount must be positive"}, status=400)
except InvalidOperation, TypeError:
return JsonResponse({"error": "Invalid amount"}, status=400)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| return json.loads(request.body) # type: ignore[no-any-return] | ||
| payload = json.loads(request.body) | ||
| except json.JSONDecodeError, ValueError: |
Comment on lines
147
to
151
| if order_id: | ||
| try: | ||
| order = Order.objects.get(pk=int(order_id), conference=self.conference) # type: ignore[arg-type] | ||
| except Order.DoesNotExist, TypeError, ValueError: | ||
| return JsonResponse({"error": "Order not found"}, status=404) |
Comment on lines
647
to
650
| try: | ||
| quantity = int(item_data.get("quantity", 1)) # type: ignore[arg-type] | ||
| except (TypeError, ValueError): | ||
| except TypeError, ValueError: | ||
| return JsonResponse({"error": "Invalid quantity value"}, status=400) |
Comment on lines
660
to
663
| try: | ||
| tt = TicketType.objects.get(pk=int(ticket_type_id), conference=self.conference) # type: ignore[arg-type] | ||
| except (TicketType.DoesNotExist, TypeError, ValueError): | ||
| except TicketType.DoesNotExist, TypeError, ValueError: | ||
| return None |
Comment on lines
677
to
680
| try: | ||
| addon = AddOn.objects.get(pk=int(addon_id), conference=self.conference) # type: ignore[arg-type] | ||
| except (AddOn.DoesNotExist, TypeError, ValueError): | ||
| except AddOn.DoesNotExist, TypeError, ValueError: | ||
| return None |
Comment on lines
214
to
+227
| with transaction.atomic(): | ||
| if order is None: | ||
| order_user = request.user | ||
| attendee_code = str((body or {}).get("attendee_access_code", "")).strip() | ||
| if attendee_code: | ||
| try: | ||
| attendee = CheckInService.lookup_attendee( | ||
| conference=self.conference, | ||
| access_code=attendee_code, | ||
| ) | ||
| order_user = attendee.user | ||
| except Attendee.DoesNotExist: | ||
| return JsonResponse({"error": "Attendee not found"}, status=404) | ||
|
|
Comment on lines
1305
to
1316
| // ---- Voucher ---- | ||
| // NOTE: CartOperationsView does not currently support server-side voucher | ||
| // validation. Voucher application is handled as a client-side placeholder | ||
| // that stores the code for later submission during checkout. If the backend | ||
| // gains voucher support, update this function accordingly. | ||
|
|
||
| async function applyVoucher() { | ||
| var code = voucherInput.value.trim(); | ||
| if (!code) return; | ||
|
|
||
| // For now, store the voucher code locally. The actual discount would need | ||
| // to be validated server-side when the backend supports it. We show the | ||
| // voucher as "pending" so the operator knows it was entered. | ||
| appliedVoucher = {code: code, discount: 0}; | ||
| voucherAppliedText.textContent = "Voucher " + code + " (pending server validation)"; | ||
| voucherApplied.classList.add("visible"); | ||
| voucherRow.style.display = "none"; | ||
| renderCart(); | ||
| voucherInput.value = ""; | ||
| alert("Vouchers are not yet supported for terminal sales. Use the online checkout flow for voucher redemptions."); | ||
| } |
Comment on lines
+1361
to
+1378
| // If we have cart items but no order yet, checkout first to create a proper order | ||
| if (!currentOrderId && cart.length > 0) { | ||
| var checkoutPayload = {action: "checkout", billing_name: "", billing_email: ""}; | ||
| if (currentAttendee && currentAttendee.attendee && currentAttendee.attendee.access_code) { | ||
| checkoutPayload.attendee_access_code = currentAttendee && currentAttendee.attendee && currentAttendee.attendee.access_code; | ||
| var nameEl = document.getElementById("attendeeName"); | ||
| var emailEl = document.getElementById("attendeeEmail"); | ||
| if (nameEl) checkoutPayload.billing_name = nameEl.textContent || ""; | ||
| if (emailEl) checkoutPayload.billing_email = emailEl.textContent || ""; | ||
| } | ||
| var checkoutData = await apiFetch(API.cart, { | ||
| method: "POST", | ||
| body: JSON.stringify(checkoutPayload) | ||
| }); | ||
| if (checkoutData.order_id) { | ||
| currentOrderId = checkoutData.order_id; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Addresses all findings from gpt-5.4 Codex review of the Stripe Terminal POS system.
High severity fixes
process_terminal_payment()call — JS SDK drives the reader viaclient_secretattendee_access_codefor attendee ownershipMedium severity fixes
_parse_json_bodyvalidates dict: Rejects JSON arrays/stringsTerminalPayment.reader_idinstead of trusting client input_create_and_dispatchTest plan
🤖 Generated with Claude Code