Module kit / Commerce and payments
Order fulfillment and infoproduct delivery
commerce-payments/order-fulfillment-and-delivery
| Kind | backend |
|---|---|
| Domain | commerce-payments |
| Maturity | prod |
| Reusable | extract |
| Stack | fastapi-0.118, sqlalchemy-2.0-sync, postgres, alembic-1.16, pydantic-2.12 |
| Depends on | _shared/fastapi-project-layout, _shared/alembic-migrations, _shared/workspace-scoping, _shared/test-layout |
| Frontends | commerce-payments/ui/storefront-preview |
| Reference implementation |
|
| Measured on | alchemy_labs main 62ba4fc41009b454e1c033e5036c7e8577db2061 2026-09-12 |
| Version | 1 |
Purpose
When an order's status flips to paid (the Payrexx webhook's authoritative re-fetch, or a CHF 0 product's checkout short-circuit), the buyer gets exactly one thing per product kind: an infoproduct buyer gets one metered, short-TTL download link; every other Phase-1 kind gets one confirmation email; and when the order is later refunded or cancelled, an infoproduct's remaining downloads are zeroed so an already-issued link stops working. The invariant that makes this trustworthy is that fulfill_order/reverse_fulfillment never commit and never mint twice: they run inside the same transaction the caller already holds, and the one row this recipe mints, DownloadGrant, is idempotent under UNIQUE(order_id, product_id) - a re-delivered webhook re-uses the existing grant instead of minting a second one and sending a second email (module docstring, fulfillment_service.py 1 to 21).
When to use
Read it before touching fulfill_order, reverse_fulfillment, _mint_grant, _build_download_url/build_download_url, or the metered GET /commerce/download/{grant_id} endpoint's atomic UPDATE; when someone asks "why did this buyer get two download emails", "why does the link say limit reached", "does a refund kill the download", or "why didn't the confirmation email go out". Trigger phrases: "download grant", "DownloadGrant", "infoproduct fulfillment", "metered download", "commerce/download", "duplicate grant", "reverse_fulfillment", "fulfill_order", "product/workshop confirmation email".
Not this recipe: the credits branch inside both fulfill_order and reverse_fulfillment (enqueue_topup/enqueue_reversal, prima_credit_grants.py, billing-credits' territory - touched here only because it lives in the same kind-keyed if chain); subscription minting (mint_scheduled_subscription/cancel_subscription, subscription_service.py, ScheduledSubscription); the Spark provisioning hook (services/spark/provisioning.on_order_paid, flag-gated, best-effort, a no-op for every non-Spark order); the unified messaging dispatch for custom on_event confirmations (messaging_service.steps_for/dispatch, a separate recipe - cited here only because it sets has_custom_confirmation, which suppresses this recipe's own generic email); the Payrexx webhook's own order-matching, signature check and state-machine guard upstream of the paid transition (commerce_router.payrexx_webhook, that is the webhook recipe itself - this recipe starts at the two lines that call fulfill_order/reverse_fulfillment); the workshop seat-capacity backstop (services/commerce/capacity.py); the non-CHF0 Payrexx gateway-creation path in checkout_service.create_checkout (a neighbouring checkout recipe - this recipe only covers its CHF 0 short-circuit, the second caller of fulfill_order).
Stack assumptions
At 62ba4fc: fastapi==0.118.2, synchronous handlers on a sqlalchemy.orm.Session (SQLAlchemy==2.0.43), alembic==1.16.5, psycopg2-binary==2.9.10, pydantic==2.12.0, Postgres 15 in prod (docker-compose.prod.yml:9, postgres:15-alpine) / 14 in the test harness (docker-compose.test.yml:6,21,115, postgres:14). redis==6.4.0 is in requirements.txt but nothing in this recipe's files touches it. Foundations assumed: S4 (fastapi-project-layout) for app_fastapi.py's mount_router(app, commerce_router.router, prefix="/api", tags=["Commerce"], surfaces=LABS_SURFACES) (838); S8 (alembic-migrations) for the migration chain under Deploy & ops; S6 (workspace-scoping) for refund_order's VerifiedUser/WorkspaceID/IsolationService.verify_workspace_access dependencies - by contrast commerce_download and payrexx_webhook carry no auth dependency at all, workspace is resolved from the row, never a session; S9 (test-layout) for the db_session/client/test_user/test_workspace_id fixtures in tests/conftest.py this recipe's tests depend on.
Environment: FILE_TOKEN_SECRET (core/config.py 290 to 306), default "your-secret-key-change-in-production", a field_validator raises ValueError at settings load when ENVIRONMENT == "production" and the value is still the default - fails closed at boot, not at request time; API_BASE_URL (core/config.py 42), default "http://localhost:8000", the base the download link mailed to the buyer is built from.
Data model
download_grants, one row per (order_id, product_id), minted by _mint_grant (models/commerce_models.py 160 to 185):
download_grants (
id uuid PRIMARY KEY,
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
order_id uuid NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product_id uuid NOT NULL REFERENCES products(id) ON DELETE CASCADE,
file_id varchar(255) NOT NULL,
filename varchar(255),
downloads_remaining integer NOT NULL DEFAULT 5,
downloads_initial integer NOT NULL DEFAULT 5, -- refund-guard: refundable only while remaining == initial (comment, not enforced in these files)
expires_at timestamptz,
last_downloaded_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now()
);
CHECK ck_download_grants_remaining_nonneg: downloads_remaining >= 0
UNIQUE uq_download_grants_order_product: (order_id, product_id)
INDEX ix_download_grants_order: (order_id)
products.kind CHECK admits 'product','workshop','infoproduct','appointment','credits' (97); products.file_id/products.filename (37 to 38) are the infoproduct's single asset pointer, not the file itself. orders.status CHECK admits 'pending','paid','failed','refunded','cancelled','expired','paid_oversold' (153). downloads_limit and grant_expires_at for a new grant come from product.extra_metadata (JSONB), read once at mint time (152 to 160 in fulfillment_service.py): expires_at is parsed with datetime.fromisoformat and silently becomes NULL on anything malformed (157 to 160, no error surfaced).
What is NOT stored: the file's bytes (only file_id, a pointer into RenderStorageService); a balance of any kind; the download token itself (it is signed and returned in the email/response, never persisted - SecureURLGenerator is stateless).
API surface
| Route | Auth | Tenancy | Codes | Notes |
|---|---|---|---|---|
GET /api/commerce/download/{grant_id} (commerce_router.py 909 to 977) | none - no VerifiedUser/WorkspaceID param at all | grant.workspace_id, read from the row, never from a session | 200 stream; 403 invalid/expired token (925), 403 token doesn't match grant (935), 403 order not paid (940), 404 grant not found (930), 403 download limit reached or link expired (955), 404 file not found in storage (961) | Access control is the signed token plus the owning order's live status, not a session |
POST /api/orders/{order_id}/refund (1266 to 1323) | VerifiedUser + WorkspaceID, IsolationService.verify_workspace_access(..., required_role="member") | Order.workspace_id == workspace_id, row locked with_for_update() (1287 to 1291) | 200 refunded; 404 not found / cross-workspace; 403 role below member; 409 not paid/paid_oversold or no captured transaction; 502 Payrexx rejects the refund | Calls reverse_fulfillment(db, order) at 1322, after order.status = "refunded", before the single db.commit() |
POST /api/orders/webhook/payrexx (1390 to 1738) | none required; an optional X-Webhook-Signature/X-Payrexx-Signature is checked only if present AND PAYREXX_WEBHOOK_SECRET is configured (401 on mismatch) | order resolved by tx id (auto-topup only) / referenceId / gateway id, never a workspace in the request | 200 always (various ok/matched/status shapes); 401 bad signature; 400 missing tx id or tx/order mismatch; 502 could not verify with zahls | This recipe's primary call site: fulfill_order(db, order) at 1697 when new_status == "paid" (guarded by the amount/currency check at 1651 to 1663 and the re-locked, populate_existing() order fetch at 1618 to 1625); reverse_fulfillment(db, order) at 1704 when new_status is "refunded" or "cancelled" (1703). The trust boundary is the authoritative re-fetch: transaction = fresh at 1594 overwrites the untrusted POST body before any status mapping happens |
POST /api/commerce/checkout (896 to 904 → checkout_service.create_checkout) | none | workspace resolved from the product row | 200 always (pending or paid shape); 404 product missing/inactive; 409 sold out | The CHF 0 short-circuit inside create_checkout (checkout_service.py 188 to 221) is the second caller of fulfill_order, at line 198, for a free one_time product - no gateway opened, fulfill_order runs, then db.commit(), then a download_url is built for the response if the product is an infoproduct |
Two Python entrypoints, not routes, but the actual surface a rebuild reimplements: fulfill_order(db, order) -> None (fulfillment_service.py 224 to 367) and reverse_fulfillment(db, order) -> None (370 to 433). Neither commits (module docstring 19 to 21); the caller owns the single transaction.
Application logic
fulfill_order, in commit order:
- Load
Productbyorder.product_id; missing product logs an error and returns, fulfilling nothing (230 to 233). - Spark provisioning hook: flag-gated, best-effort
try/except, no-ops for every non-Spark order (239 to 251) - out of scope, cited for context. - Unified messaging dispatch (
messaging_service.steps_for/dispatch) for ANY kind, running before the kind-specific branch so it also fires for infoproducts; setshas_custom_confirmationwhen a customon_eventstep exists, which later suppresses this recipe's own generic confirmation to avoid a double send; best-efforttry/except(259 to 287) - out of scope, cited because it changes whether step 6's email fires. billing_model in ("intro_then_sub", "subscription"):mint_scheduled_subscription(294 to 296) - out of scope,subscription_service.py.- Kind fan-out:
kind == "credits":enqueue_topup, an optionalpersist_token_from_orderwhensave_cardwas set,_send_credits_receipt, then return (298 to 341) - out of scope, billing-credits' territory.kind == "infoproduct": noproduct.file_idlogs an error and returns - no grant, no email (344 to 348); otherwise_mint_grant(349),_build_download_url(350), a German-copy email naminggrant.downloads_remainingsent via_send_email, then return (351 to 358).- anything else (
product/workshop/appointment): a generic confirmation email UNLESShas_custom_confirmationis set (362 to 367).
reverse_fulfillment, in commit order:
- Load
Product; missing returns with nothing done (376 to 378). billing_model in ("intro_then_sub", "subscription"): look up theScheduledSubscriptionbyorder_id,cancel_subscriptionif found (382 to 391) - out of scope.kind == "credits":enqueue_reversal, a best-effort refund-notice email only whenextra_metadata.kind == "auto_topup", then return (393 to 424) - out of scope, billing-credits' territory.kind == "infoproduct": load everyDownloadGrantfororder_id, setdownloads_remaining = 0on each (426 to 433). No grant row is ever deleted here - the function simply falls off the end after the loop.
_mint_grant (138 to 174): idempotent by a plain SELECT on (order_id, product_id) before any INSERT (144 to 150) - not a database upsert; the actual serialization against a concurrent duplicate mint comes from the caller already holding the order row FOR UPDATE (the webhook re-locks it at 1618 to 1625), not from a lock this function takes itself. Reads downloads_limit/grant_expires_at from product.extra_metadata (152 to 160); db.flush(), not db.commit(), to populate grant.id before the signed URL is built (173).
_build_download_url (fulfillment_service.py 42 to 55) and build_download_url (checkout_service.py 250 to 267) are two independently written functions that do the same thing: resolve FILE_TOKEN_SECRET the same way, sign grant.id (not grant.file_id, so one buyer's token cannot validate against another buyer's grant for the same shared file), and build the same /api/commerce/download/{grant.id}?token=... shape with the same 600-second TTL - see Gotchas.
Invariants
- A
DownloadGrantis minted at most once per(order_id, product_id): enforced twice, the database'suq_download_grants_order_product(commerce_models.py179) and_mint_grant's query-before-insert (test_download_grant_unique_order_productproves the constraint;test_webhook_idempotent_double_deliveryproves the whole webhook path settles on exactly one grant and one email across two deliveries of the same payload). fulfill_order/reverse_fulfillmentnever commit; the caller's transaction is the unit of atomicity (module docstring 19 to 21).- An infoproduct with no
file_idfulfills nothing rather than partially (344 to 348) - no grant row, no email. - A refund/cancel zeroes
downloads_remainingon every grant for the order unconditionally; it never deletes the grant and never touchesdownloads_initial(426 to 433;test_refund_webhook_reverses_fulfilment). - The metered download endpoint's per-grant limit is enforced by one atomic conditional
UPDATE ... WHERE downloads_remaining > 0 AND (expires_at IS NULL OR expires_at > now()) RETURNING file_id(commerce_router.py944 to 952), never a read-then-write in application code;test_download_atomic_decrement_no_over_downloadseedsdownloads_remaining=1and proves the firstGETis 200, the second is 403, and the row lands at exactly 0. - The download endpoint checks the OWNING ORDER's live
status == "paid"(938 to 940) independently of the token's own validity - a grant already zeroed byreverse_fulfillment, or one whose order never paid, cannot be downloaded even with a structurally valid, unexpired token (test_download_requires_paid_order).
Frontend
The module that would render this, by id:
commerce-payments/ui/storefront-preview the buyer-facing surface this fan-out's effects reach: the
checkout response's download_url (CHF0 short-circuit)
and the confirmation/download email's link. At this
commit nothing in the read files renders a merchant-
facing admin panel for DownloadGrant state - the
admin surface below is the closest thing that exists
Minimum admin/debug surface (no dedicated route exists at this commit): SELECT id, order_id, product_id, downloads_remaining, downloads_initial, expires_at, last_downloaded_at FROM download_grants WHERE order_id = :order_id.
Security & tenancy
commerce_download: no auth dependency at all - access control is entirely the signed token (SecureURLGenerator, HMAC-SHA256 via FILE_TOKEN_SECRET) plus the owning order's live status. Every guard raises HTTPException (403 or 404) rather than falling through to a default-permit branch (925, 930, 935, 940, 955, 961). Workspace derivation: grant.workspace_id, read from the row; verify_download_token also returns a workspace_id from the decoded payload (url_generator.py 129), but commerce_router.py never compares it to grant.workspace_id - the only binding check performed is v.get("file_id") != str(grant_id) (934). Not independently exploitable through this endpoint alone (the token is signed to one specific grant UUID), but flagged in Gotchas as a value this endpoint decodes and never verifies. No rate-limit or slowapi decorator exists on this route or the webhook (grep of commerce_router.py); the only bound on repeated use of one token is downloads_remaining itself and the token's own expiry.
payrexx_webhook: unauthenticated by design, the trust boundary is the authoritative re-fetch, not the POST body - transaction = fresh (1594) overwrites the caller-supplied payload before tx_status is ever read, and fulfill_order runs only after that. A mismatched amount or currency blocks the paid transition (and therefore fulfill_order) before either is called (1651 to 1663; test_webhook_amount_mismatch_blocks_fulfilment proves no grant, no email, order stays pending).
refund_order: VerifiedUser + Order.workspace_id == workspace_id with a with_for_update() row lock (1287 to 1291), then IsolationService.verify_workspace_access(..., required_role="member") (1298 to 1303) - a viewer-role membership passes the WorkspaceID dependency itself but is refused here. No test in this file exercises refund_order for cross-workspace rejection directly; test_attach_asset_cross_workspace_rejected proves the same IsolationService pattern on a neighbouring admin route in this file, not this one - a genuine gap, not invented coverage.
Secrets at rest: none minted by this recipe beyond the ephemeral, unstored download token; FILE_TOKEN_SECRET is an env var never logged (grep of the five read files for a token being logged finds nothing). Attacker-controlled inputs: the token query param (structurally arbitrary, verified by signature and expiry only) and the grant_id path param (UUID-typed by FastAPI; an unknown id 404s at the DB load, 930).
Tests
tests/test_commerce_phase1.py, 25 def test_ functions (grep -c "^def test_"), 0 @pytest.mark.parametrize. Run 2026-09-12 against an isolated scratch Postgres created for this measurement (the shared default postgresql://test_user:test_password@localhost:5434/test_db deadlocked twice under Base.metadata.drop_all - see Gotchas - consistent with another agent's concurrent use of that same database):
TEST_DATABASE_URL=postgresql://test_user:test_password@localhost:5434/commerce_recipe_scratch \
python -m pytest tests/test_commerce_phase1.py -q --no-cov -o addopts="" -p no:cacheprovider
prints 25 passed (360 warnings: Pydantic v2 class-Config deprecations elsewhere in the app, plus a LegacyAPIWarning on db.query(...).get(...) at commerce_router.py 928 and 938 - see Gotchas). Schema comes from tests/conftest.py's session-scoped Base.metadata.create_all, not alembic upgrade head, for this file's tests.
Hermetic: mock_payrexx stubs gateway creation; the webhook's authoritative re-fetch is stubbed by the autouse _stub_webhook_refetch fixture against a module-level _TX_REGISTRY dict, never a live zahls call; mock_email stubs email_service._send_via_resend; stub_storage stubs RenderStorageService.file_exists/get_file_size/read_file_stream so the download endpoint streams without a real file. No live network anywhere in this file.
Tests this recipe's scope owns directly: test_download_grant_unique_order_product, test_download_grant_remaining_nonneg_constraint (the two DownloadGrant constraints); test_checkout_free_short_circuit (CHF0 → fulfill_order via checkout_service); test_webhook_idempotent_double_delivery, test_webhook_grant_mint_and_email, test_webhook_amount_mismatch_blocks_fulfilment (webhook → fulfill_order); test_download_atomic_decrement_no_over_download, test_download_requires_paid_order, test_download_bad_token_rejected (the metered endpoint); test_refund_webhook_reverses_fulfilment (reverse_fulfillment zeroing). The remaining 16 tests in the file exercise neighbouring recipes (migration identity, Product/Order CHECK constraints unrelated to DownloadGrant, checkout amount derivation, workshop capacity) and are not re-described here.
Known-answer vector: _payrexx_paid_payload's transaction id 999001 is the fixed literal _TX_REGISTRY keys on; the fixture's own comment (93 to 96) documents it as the test's substitute for zahls' authoritative re-fetch.
What a rebuild adds and this branch lacks: no test asserts the download endpoint's decoded workspace_id against anything (there is nothing to assert - see Gotchas); no test exercises refund_order specifically for cross-workspace rejection; no Playwright/E2E spec exists for the download link a buyer actually clicks (this is a webhook-plus-streaming-response surface, no browser flow in this repo).
Deploy & ops
Config keys: FILE_TOKEN_SECRET, API_BASE_URL (both above). No scheduler job: unlike a retry-driven engine, this fan-out runs synchronously inside the request that flips the order (the webhook or the CHF0 checkout), never on an APScheduler tick - grep of scheduled_jobs.py for fulfill_order/reverse_fulfillment/download_grants returns nothing. Migrations, in chain order (backend/migrations/versions/): 20260609_0001_commerce_kind_infoproducts.py (commerce_kind_infoproducts_001, down-revision review_original_snapshot_001) creates download_grants and products.kind/file_id/filename, widens ck_orders_status; 20260610_0004_commerce_subscriptions.py adds billing_model and its columns (not this recipe's table, cited for the chain); 20260903_0001_commerce_image_content_emdash.py and 20260903_0003_commerce_second_image.py add product image/content columns (out of scope). Verified 2026-09-12: alembic upgrade head from an empty database completes cleanly through 98 revision files to one head (prima_device_codes_001), no error beyond the INFO [alembic.runtime.migration] lines.
Argus-style check: GET /api/commerce/download/{grant_id}?token=garbage returns 403 (test_download_bad_token_rejected proves the shape). No dedicated health route exists for this fan-out at this commit. Rollback: nothing deployable beyond the code and its one owning migration; reverse_fulfillment never deletes a download_grants row (only zeroes it), so a code rollback needs no data unwind.
Gotchas
- 2026-09-12 (measured at 62ba4fc): two independently written implementations of the signed download URL exist -
fulfillment_service._build_download_url(42 to 55) andcheckout_service.build_download_url(250 to 267) - same signing-key resolution, same signed field (grant.id), sameDOWNLOAD_TOKEN_TTL_SECONDS = 600defined separately in each file.fulfillment_service.py's own comment (194 to 196) explains why the import runs the way it does (a circular-import dodge:checkout_service.create_checkoutimportsfulfill_orderlazily, not the reverse), but nothing explains why the URL builder itself is duplicated rather than shared. A change to the token shape in one (say, adding a claim) does not propagate to the other. - 2026-09-12 (measured at 62ba4fc,
commerce_router.py922 to 935):SecureURLGenerator.verify_download_tokenreturns aworkspace_idfrom the decoded payload (url_generator.py129), butcommerce_downloadnever reads it or compares it togrant.workspace_id- the only binding check performed isv.get("file_id") != str(grant_id). Not independently exploitable through this endpoint today (the token is bound to one unguessable grant UUID), but a rebuild that starts trusting the token'sworkspace_idfor anything would be trusting a value this endpoint currently never verifies against the row it names. - 2026-09-12 (measured at 62ba4fc):
db.query(...).get(...)atcommerce_router.py928 and 938 is SQLAlchemy 2.0's legacyQuery.get(), flagged by aLegacyAPIWarningobserved directly in the test run above (Tests). Functionally correct at this commit; cited because it is exactly the kind of call that breaks on a future SQLAlchemy major bump. - 2026-09-12 (measured at 62ba4fc): two live-DB runs of this recipe's own test file against the repo's default shared scratch database (
postgresql://test_user:test_password@localhost:5434/test_db) both ended in a PostgresDeadlockDetectedduringBase.metadata.drop_all(on aleads/usersforeign-key drop), consistent with another process concurrently dropping/recreating the same schema. An isolated database gave a clean25 passed. This is a property of the test harness's session-scopeddrop_all/create_allunder concurrent invocation, not a defect in this recipe's code or tests - worth knowing before reading a single flaky local run as a real failure.
Reference implementation
Paths relative to the repo root; all at main 62ba4fc41009b454e1c033e5036c7e8577db2061 unless noted.
alchemy_labs/backend/services/commerce/fulfillment_service.py(433 lines): module docstring (1 to 21);_build_download_url(42 to 55);_event_anchor(58 to 105, out of scope, cited for context);_message_variables(108 to 135, out of scope);_mint_grant(138 to 174);_send_email(177 to 184);_send_credits_receipt(187 to 221, out of scope, billing-credits);fulfill_order(224 to 367);reverse_fulfillment(370 to 433).alchemy_labs/backend/models/commerce_models.py(270 lines):Product(20 to 107,kind/billing_modelCHECKs at 97 to 101);Order(110 to 157,statusCHECK at 153);DownloadGrant(160 to 185, all columns/CHECK/UNIQUE);ScheduledSubscription(187 to 246, out of scope);SubscriptionCancelOtp(248 to 270, out of scope).alchemy_labs/backend/api/routers/commerce_router.py(1738 lines): imports (38 to 45);commerce_checkout(896 to 904);commerce_download(909 to 977);refund_order(1266 to 1323);payrexx_webhook(1390 to 1738 - this recipe's scope is thepaid/refunded/cancelledtransition and itsfulfill_order/reverse_fulfillmentcalls at 1697/1704, not the order-matching/signature logic upstream of it).alchemy_labs/backend/services/commerce/checkout_service.py(267 lines):create_checkout(37 to 247), the CHF0 short-circuit at 188 to 221 (the second caller offulfill_order, at line 198);build_download_url(250 to 267, the duplicate builder, see Gotchas).alchemy_labs/backend/services/storage/url_generator.py(330 lines, whole file read):SecureURLGenerator.__init__(20 to 27);generate_download_token(29 to 79);verify_download_token(81 to 139);_sign_payload(283 to 299).generate_upload_token/verify_upload_token/generate_share_link/verify_share_password/generate_direct_download_url(141 to 282, 301 to 330) are unused by this recipe's call sites, cited for completeness only.alchemy_labs/backend/migrations/versions/20260609_0001_commerce_kind_infoproducts.py: the one migration this recipe's schema (download_grants,products.kind/file_id/filename) is defined in.alchemy_labs/backend/tests/test_commerce_phase1.py(831 lines, whole file read): fixtures (39 to 241); the 25 tests listed under Tests.
Deviations, all dated 2026-09-12: two independently-maintained download-URL builders instead of one shared function (Gotchas); the download endpoint decodes but never verifies the token's own workspace_id claim against the grant row; no admin/debug route exists for DownloadGrant state beyond the SQL query under Frontend; refund_order's cross-workspace rejection is untested in this file (a neighbouring route's test covers the same IsolationService pattern, not this endpoint).
Acceptance checks
Run from alchemy_labs/backend at 62ba4fc against a scratch Postgres not shared with a concurrent writer. Expected observations are exact, as measured 2026-09-12.
createdb commerce_recipe_checkthenSYNC_DATABASE_URL=postgresql://localhost:5434/commerce_recipe_check DATABASE_URL=postgresql://localhost:5434/commerce_recipe_check ENVIRONMENT=development WEBAUTHN_RP_ID_BACKFILL=localhost python -m alembic upgrade headcompletes with no error beyond theINFO [alembic.runtime.migration]lines, ending on headprima_device_codes_001.TEST_DATABASE_URL=postgresql://test_user:test_password@localhost:5434/commerce_recipe_check python -m pytest tests/test_commerce_phase1.py -q --no-cov -o addopts="" -p no:cacheproviderprints25 passed.grep -c "^def test_" tests/test_commerce_phase1.pyprints25;grep -c "@pytest.mark.parametrize" tests/test_commerce_phase1.pyprints0- the 25 defs are exactly the 25 collected and passed tests, no parametrize expansion.grep -n "UniqueConstraint('order_id', 'product_id'" models/commerce_models.pyprints theDownloadGrantUNIQUE at line179.grep -n "^def fulfill_order\|^def reverse_fulfillment" services/commerce/fulfillment_service.pyprints224and370;grep -rn "fulfill_order(db, order)" api/routers/commerce_router.py services/commerce/checkout_service.pyprints exactly two call sites (commerce_router.py:1697,checkout_service.py:198), confirming the webhook'spaidtransition and the CHF0 short-circuit are its only two callers.grep -n "downloads_remaining = downloads_remaining - 1" api/routers/commerce_router.pyprints the one atomic conditional UPDATE, at line946, confirming the metered decrement is not read-then-write application code.
Content synced from the prima-modules repository at 0df2ec8 on 2026-09-14. The recipe file is the source; this page is a rendering of it.