Skip to content

fix issues with orders api - #3823

Open
gumaerc wants to merge 1 commit into
mainfrom
cg/orders-api-fixes
Open

fix issues with orders api#3823
gumaerc wants to merge 1 commit into
mainfrom
cg/orders-api-fixes

Conversation

@gumaerc

@gumaerc gumaerc commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

https://github.com/mitodl/hq/issues/12684

Description (What does it do?)

Fixes three things that were fundamentally broken about the v0 Orders API, so we
don't have to work around them in the new receipt page.

  1. purchaser was typed as an array but returns an object. get_purchaser was
    annotated many=True, so generated clients declared
    Array<ExtendedLegalAddress> for a single object — unusable without casting
    around the type.

  2. orders/history returned a different shape depending on whether limit was
    passed.
    OrderHistoryViewSet had no default_limit, so DRF skipped pagination
    entirely and returned a bare list; with any limit it returned the documented
    {count, next, previous, results} envelope.

  3. The receipt endpoint 500s when a product's courseware object has been deleted.
    TransactionLineSerializer dereferenced content_object.start_date without
    checking that the GenericForeignKey resolved.

Scoped to v0, which is what the generated clients target. The unversioned
/api/orders/... duplicate isn't in the spec and produces no TypeScript, so it's
left alone.

Only (1) changes the OpenAPI spec (arrayallOf). Response bodies are
unchanged, so the oasdiff findings are recorded as a spec correction.

How can this be tested?

Data you need: one fulfilled order on your own account. If you don't have one,
make a 100%-off code and use it to create a verified enrollment, which fulfills an
order as a side effect:

./manage.py generate_discount_code --count 1 --prefix RECEIPT_TEST_ \
    --amount 100 --discount-type percent-off --payment-type staff

./manage.py create_verified_enrollment --user=<your email> \
    --run=<course_run_courseware_id> --code=<the generated code>

Note the order id from /api/v0/orders/history/ for the checks below.

(2) — order history shape. Hit the endpoint both ways:

GET /api/v0/orders/history/            # on main: bare JSON array
                                       # here:    {count, next, previous, results}
GET /api/v0/orders/history/?limit=10   # envelope either way

(1) — purchaser type. GET /api/v0/orders/receipt/<order id>/ and confirm
purchaser is a single object, not a list. That was always true at runtime; the fix
is that openapi/specs/v0.yaml now agrees, so a regenerated client types it as an
object. Worth a glance at the spec diff to confirm.

(3) — dangling courseware object. Delete the course run behind the order's
product, then reload the receipt:

./manage.py shell -c "
from courses.models import CourseRun
CourseRun.objects.filter(courseware_id='<course_run_courseware_id>').delete()
"

GET /api/v0/orders/receipt/<order id>/ 500s on main and returns 200 here, with
null start_date/end_date on the line. (Do this on a throwaway run — it also
removes the enrollment.)

@gumaerc
gumaerc force-pushed the cg/orders-api-fixes branch from 9b4655a to 3fc62b3 Compare August 5, 2026 21:20
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

OpenAPI Changes

Show/hide changes
## Changes for v0.yaml:
12 changes: 6 error, 0 warning, 6 info
error	[response-property-type-changed] at head/openapi/specs/v0.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		the `results/items/redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v0.yaml
	in API POST /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `201`

error	[response-property-type-changed] at head/openapi/specs/v0.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v0.yaml
	in API PATCH /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v0.yaml
	in API PUT /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v0.yaml
	in API GET /api/v0/orders/receipt/{id}/
		the `purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

info	[response-property-all-of-added] at head/openapi/specs/v0.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		added `#/components/schemas/ExtendedLegalAddress` to the `results/items/redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v0.yaml
	in API POST /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `201`

info	[response-property-all-of-added] at head/openapi/specs/v0.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v0.yaml
	in API PATCH /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v0.yaml
	in API PUT /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v0.yaml
	in API GET /api/v0/orders/receipt/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `purchaser` response property `allOf` list for the response status `200`



## Changes for v1.yaml:
12 changes: 6 error, 0 warning, 6 info
error	[response-property-type-changed] at head/openapi/specs/v1.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		the `results/items/redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v1.yaml
	in API POST /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `201`

error	[response-property-type-changed] at head/openapi/specs/v1.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v1.yaml
	in API PATCH /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v1.yaml
	in API PUT /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v1.yaml
	in API GET /api/v0/orders/receipt/{id}/
		the `purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

info	[response-property-all-of-added] at head/openapi/specs/v1.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		added `#/components/schemas/ExtendedLegalAddress` to the `results/items/redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v1.yaml
	in API POST /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `201`

info	[response-property-all-of-added] at head/openapi/specs/v1.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v1.yaml
	in API PATCH /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v1.yaml
	in API PUT /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v1.yaml
	in API GET /api/v0/orders/receipt/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `purchaser` response property `allOf` list for the response status `200`



## Changes for v2.yaml:
12 changes: 6 error, 0 warning, 6 info
error	[response-property-type-changed] at head/openapi/specs/v2.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		the `results/items/redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v2.yaml
	in API POST /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `201`

error	[response-property-type-changed] at head/openapi/specs/v2.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v2.yaml
	in API PATCH /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v2.yaml
	in API PUT /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		the `redeemed_order/purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

error	[response-property-type-changed] at head/openapi/specs/v2.yaml
	in API GET /api/v0/orders/receipt/{id}/
		the `purchaser` response's property `type` changed from `array<object>` to `any` for status `200`

info	[response-property-all-of-added] at head/openapi/specs/v2.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		added `#/components/schemas/ExtendedLegalAddress` to the `results/items/redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v2.yaml
	in API POST /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `201`

info	[response-property-all-of-added] at head/openapi/specs/v2.yaml
	in API GET /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v2.yaml
	in API PATCH /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v2.yaml
	in API PUT /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `redeemed_order/purchaser` response property `allOf` list for the response status `200`

info	[response-property-all-of-added] at head/openapi/specs/v2.yaml
	in API GET /api/v0/orders/receipt/{id}/
		added `#/components/schemas/ExtendedLegalAddress` to the `purchaser` response property `allOf` list for the response status `200`



Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@gumaerc
gumaerc force-pushed the cg/orders-api-fixes branch from 3fc62b3 to 02500b6 Compare August 12, 2026 18:38
Three narrow fixes to the v0 orders endpoints, which are what the generated
`@mitodl/mitxonline-api-axios` clients target (`/api/v0/orders/receipt/{id}/` and
`/api/v0/orders/history/`). The unversioned `/api/orders/...` implementation is a
separate near-duplicate that is not in the generated spec, so it is left alone.

1. `get_purchaser` was annotated `ExtendedLegalAddressSerializer(many=True)` but
   returns a single object. Generated clients therefore declared
   `Array<ExtendedLegalAddress>` for an object, so a consumer could not read
   `purchaser.email` without casting around the type.

2. `OrderHistoryViewSet` used a bare `LimitOffsetPagination`, whose `default_limit`
   is None. DRF skips pagination entirely in that case, so omitting `limit`
   returned a bare list while passing any `limit` returned the
   `{count, next, previous, results}` envelope. The generated spec has always
   declared the envelope, so a client that trusted the type and omitted `limit`
   would read `.results` off an array. Two tests that asserted the bare list are
   updated and one now pins the envelope for a request with no pagination
   parameters.

3. `TransactionLineSerializer` dereferenced `content_object.start_date` without
   checking that `purchasable_object` resolved. It is a GenericForeignKey with no
   database constraint, so deleting a courseware object leaves the product pointing
   at nothing and the receipt endpoint returns a 500. Added a regression test that
   deletes the course run and asserts the line still serializes.

Only (1) changes the OpenAPI spec, from `array` to `allOf`. That is a spec
correction rather than a behavioral change — response bodies are unchanged — so the
six oasdiff findings are recorded in oasdiff-err-ignore.txt with that reasoning.

The DRF lint baseline is regenerated. Beyond the line shifts in the file this
change touches, it also picks up five stale line numbers for users/serializers.py
that had drifted on main; the violation set per file and code is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gumaerc
gumaerc force-pushed the cg/orders-api-fixes branch from 02500b6 to 9d494dc Compare August 12, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant