fix(checkout): verify settled payment before completing checkout#119
Open
valter-silva-au wants to merge 1 commit into
Conversation
complete_checkout_session created an order after only a readiness check (line items, buyer, fulfillment present) and ignored payment_data entirely. A client could complete checkout with an arbitrary token and receive a confirmed order with no settled payment (issue NVIDIA-AI-Blueprints#112). Gate order creation on a settled payment that is bound to the session. Before releasing the order, verify_payment_for_session requires a COMPLETED PaymentIntent reached via the presented vault token, whose allowance is bound to this checkout session, that covers the server-computed total (never a caller-supplied amount), in the session currency. Otherwise the merchant responds 402 Payment Required (invalid_payment) and no order is created. The merchant reads the PSP's settled state from the shared database; importing the PSP models also registers their tables on the shared SQLModel metadata. The verification runs after the readiness check, so not-ready (405), not-found (404), and already-completed (405) behaviour is unchanged. Fixes NVIDIA-AI-Blueprints#112 Signed-off-by: Valter Silva <valter.silva.au@gmail.com>
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
complete_checkout_sessioncreated an order after only a readiness check (line items, buyer, and fulfillment present) and ignoredpayment_dataentirely — the parameter was annotated# noqa: ARG001 # Reserved for payment validation. A client could complete a checkout with an arbitrary token and receive a confirmed order with no settled payment, releasing the paid resource before any confirmed transfer.Fixes #112.
The invariant this enforces
An order is now created only when a settled payment is bound to the session. Before releasing the order,
verify_payment_for_sessionrequires aPaymentIntentthat is:status == COMPLETED;payment_data.token;allowance.checkout_session_idmatches the session being completed (a payment settled for another session cannot be replayed here);amount >=the server-computed session total (read fromtotals_json, never a caller-supplied amount — this addresses the issue's "PSP allowance is client supplied and not bound to merchant total" point);If any clause fails, the merchant returns
402 Payment Requiredwith codeinvalid_paymentand no order is created.How the merchant sees the payment
The PSP and merchant share the same database (see
src/payment/config.py— "Database (shared with merchant service)"). Importing the PSP models into the checkout service also registers their tables on the sharedSQLModelmetadata, so the merchant reads the settledpayment_intent/vault_tokenstate in-process — no cross-service HTTP call. This is a verify-only gate: it confirms a completed payment exists; it does not itself initiate payment (that remains the PSP'screate_and_process_payment_intent).Ordering / backwards-compatibility
The payment check runs after the existing readiness check, so prior behaviour is preserved: not-found →
404, not-ready →405, already-completed →405. The new failure mode is402for a ready session lacking a verified payment. Callers that previously completed a checkout without settling a payment will now correctly receive402instead of a free order — an intended behaviour change.Tests
TestCompleteCheckoutPaymentVerificationcovers the bypass and every binding clause: no settled payment, underpayment, payment bound to another session, currency mismatch, pending (not completed) intent → all402; and the happy path (settled, sufficient, matching) →200+ order.ruff check,ruff format --check, andpyright(strict) all clean — verified on Linux/Python 3.12.DCO: commit is signed off.