Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Release Notes
=============

Version 0.77.8
--------------

- Batch the PostHog view-event loader instead of querying per event (#3785)
- feat: Collect required compliance fields before any enrollment or checkout (#3766)
- receipt page (#3717)
- Allow for null course ids in webhook payload (#3776)
- make healthcheck resistant to k8 pod culling (#3760)

Version 0.77.7 (Released August 18, 2026)
--------------

Expand Down
2 changes: 1 addition & 1 deletion frontends/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
"dependencies": {
"@mitodl/mit-learn-api-axios": "2026.7.22",
"@mitodl/mitxonline-api-axios": "2026.8.6",
"@mitodl/mitxonline-api-axios": "2026.8.18",
"@tanstack/react-query": "^5.66.0",
"axios": "^1.12.2",
"tiny-invariant": "^1.3.3"
Expand Down
28 changes: 14 additions & 14 deletions frontends/api/src/generated/v1/api.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 28 additions & 1 deletion frontends/api/src/mitxonline/hooks/orders/queries.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { queryOptions } from "@tanstack/react-query"
import { ordersApi } from "../../clients"
import type { Order } from "@mitodl/mitxonline-api-axios/v2"
import type {
Order,
OrdersApiOrdersHistoryListRequest,
PaginatedOrderHistoryList,
} from "@mitodl/mitxonline-api-axios/v2"

const orderKeys = {
root: ["mitxonline", "orders"],
receipt: (orderId: number) => [...orderKeys.root, "receipt", orderId],
historyList: (opts: OrdersApiOrdersHistoryListRequest) => [
...orderKeys.root,
"history",
opts,
],
}

const orderQueries = {
Expand All @@ -19,6 +28,24 @@ const orderQueries = {
.then((res) => res.data)
},
}),
/**
* Fulfilled and refunded orders, most recent first.
*
* Enrollments carry no reference to their order, so getting from a run or
* program to its receipt means searching these lines — see
* `useOrderIdForRun` / `useOrderIdForProgram`.
*
* `opts` is required rather than defaulted: older deployments drop the
* pagination envelope when `limit` is absent and return a bare array, which
* would not match the declared return type.
*/
historyList: (opts: OrdersApiOrdersHistoryListRequest) =>
queryOptions({
queryKey: orderKeys.historyList(opts),
queryFn: async (): Promise<PaginatedOrderHistoryList> => {
return ordersApi.ordersHistoryList(opts).then((res) => res.data)
},
}),
}

export { orderQueries, orderKeys }
138 changes: 133 additions & 5 deletions frontends/api/src/mitxonline/test-utils/factories/orders.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { faker } from "@faker-js/faker/locale/en"
import type { Order, TransactionLine } from "@mitodl/mitxonline-api-axios/v2"
import type {
Line,
Nested,
Order,
OrderHistory,
OrderRefundsInner,
OrderStreetAddress,
OrderTransactions,
PaginatedOrderHistoryList,
Product,
RedeemedDiscount,
TransactionLine,
} from "@mitodl/mitxonline-api-axios/v2"

const transactionLine = (
overrides: Partial<TransactionLine> = {},
Expand All @@ -17,19 +29,135 @@ const transactionLine = (
...overrides,
})

const orderTransactions = (
overrides: Partial<OrderTransactions> = {},
): OrderTransactions => ({
card_number: `xxxxxxxxxxxx${faker.string.numeric(4)}`,
card_type: "Visa",
name: faker.person.fullName(),
bill_to_email: faker.internet.email(),
payment_method: "card",
...overrides,
})

const orderStreetAddress = (
overrides: Partial<OrderStreetAddress> = {},
): OrderStreetAddress => ({
line: [faker.location.streetAddress()],
postal_code: faker.location.zipCode(),
state: faker.location.state({ abbreviated: true }),
city: faker.location.city(),
country: "US",
...overrides,
})

const redeemedDiscount = (
overrides: Partial<Nested> = {},
): RedeemedDiscount => ({
redeemed_discount: {
id: faker.number.int(),
created_on: faker.date.past().toISOString(),
updated_on: faker.date.past().toISOString(),
amount: faker.commerce.price({ min: 5, max: 50 }),
discount_type: "dollars-off",
redemption_type: "one-time",
discount_code: faker.string.alphanumeric(12),
...overrides,
},
})

const orderRefund = (
overrides: Partial<OrderRefundsInner> = {},
): OrderRefundsInner => ({
amount: Number(faker.commerce.price({ min: 5, max: 500 })),
date: faker.date.past().toISOString(),
...overrides,
})

const order = (overrides: Partial<Order> = {}): Order => ({
id: faker.number.int(),
state: "fulfilled",
purchaser: [],
purchaser: {
country: "US",
email: faker.internet.email(),
},
total_price_paid: faker.commerce.price({ min: 50, max: 500 }),
lines: [transactionLine()],
discounts: [],
refunds: [],
refund_eligible: false,
reference_number: faker.string.alphanumeric(10),
created_on: faker.date.past().toISOString(),
transactions: {},
street_address: {},
transactions: orderTransactions(),
street_address: orderStreetAddress(),
...overrides,
})

export { order, transactionLine }
/**
* The default `purchasable_object` has only an `id`, which matches no variant —
* pass a shaped object (with `course`, or neither `course` nor `run_tag`) when the
* test needs it to resolve.
*/
const product = (overrides: Partial<Product> = {}): Product => ({
id: faker.number.int(),
price: faker.commerce.price({ min: 50, max: 500 }),
description: faker.commerce.productDescription(),
is_active: true,
purchasable_object: { id: faker.number.int() },
...overrides,
})

const line = (overrides: Partial<Line> = {}): Line => {
const unitPrice = faker.commerce.price({ min: 50, max: 500 })
return {
id: faker.number.int(),
quantity: 1,
item_description: faker.commerce.productName(),
unit_price: unitPrice,
total_price: unitPrice,
product: product(),
...overrides,
}
}

const orderHistory = (overrides: Partial<OrderHistory> = {}): OrderHistory => ({
id: faker.number.int(),
state: "fulfilled",
reference_number: faker.string.alphanumeric(10),
purchaser: {
id: faker.number.int(),
name: faker.person.fullName(),
created_on: faker.date.past().toISOString(),
updated_on: faker.date.past().toISOString(),
},
total_price_paid: faker.commerce.price({ min: 50, max: 500 }),
lines: [line()],
created_on: faker.date.past().toISOString(),
titles: [],
updated_on: faker.date.past().toISOString(),
refund_eligible: false,
...overrides,
})

const orderHistoryList = (
results: OrderHistory[],
opts: { count?: number; next?: string | null; previous?: string | null } = {},
): PaginatedOrderHistoryList => ({
count: opts.count ?? results.length,
next: opts.next ?? null,
previous: opts.previous ?? null,
results,
})

export {
order,
orderHistory,
orderHistoryList,
orderRefund,
orderStreetAddress,
orderTransactions,
line,
product,
redeemedDiscount,
transactionLine,
}
12 changes: 11 additions & 1 deletion frontends/api/src/mitxonline/test-utils/factories/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ const enforcerId = new UniqueEnforcer()

const legalAddress = (): LegalAddress => ({
country: faker.location.countryCode(),
state: faker.datatype.boolean() ? faker.location.state() : null,
// Real values are ISO-3166-2 codes (e.g. "US-MA"), not the full names
// faker.location.state() returns, and only a handful of countries even
// have subdivisions. A random name for a random country is never a value
// a state <select> would recognize, so it renders as an out-of-range MUI
// warning whenever the random country happens to require one (e.g. "US") --
// default to null and let tests that care override with a real code.
state: null,
})

const userProfile = (): UserProfile => ({
Expand Down Expand Up @@ -62,6 +68,10 @@ const user: PartialFactory<User> = (overrides = {}): User => {
updated_on: faker.date.recent().toISOString(),
grants: [],
is_active: true,
// Read-only signal from mitxonline's export-compliance check. Empty means
// the profile is complete; override with field names (e.g.
// ["city", "postal_code"]) to exercise the just-in-time dialog.
compliance_missing_fields: [],
b2b_organizations: [organization({})],
},
overrides,
Expand Down
3 changes: 3 additions & 0 deletions frontends/api/src/mitxonline/test-utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
CoursesApiCourseVariantRunsV3Request,
CourseCertificatesApiCourseCertificatesRetrieveRequest,
ProgramCertificatesApiProgramCertificatesRetrieveRequest,
OrdersApiOrdersHistoryListRequest,
ProgramCollectionsApiProgramCollectionsListRequest,
ProgramsApiProgramsListV2Request,
} from "@mitodl/mitxonline-api-axios/v2"
Expand Down Expand Up @@ -155,6 +156,8 @@ const baskets = {
const orders = {
receipt: (orderId: number) =>
`${getApiBaseUrl()}/api/v0/orders/receipt/${orderId}/`,
historyList: (params?: OrdersApiOrdersHistoryListRequest) =>
`${getApiBaseUrl()}/api/v0/orders/history/${queryify(params)}`,
}

const verifiedProgramEnrollments = {
Expand Down
2 changes: 1 addition & 1 deletion frontends/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@mitodl/arithmix": "^0.2.3",
"@mitodl/course-search-utils": "^3.5.2",
"@mitodl/hacksnack": "^0.1.1",
"@mitodl/mitxonline-api-axios": "2026.8.6",
"@mitodl/mitxonline-api-axios": "2026.8.18",
"@mitodl/smoot-design": "6.31.1",
"@mui/base": "5.0.0-beta.70",
"@mui/material": "^6.4.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { urls, factories } from "api/mitxonline-test-utils"
import {
createCoursesWithContractRuns,
createTestContracts,
setupOrderHistory,
setupOrgAndUser,
setupProgramsAndCourses,
setupOrgDashboardMocks,
setupProgramsAndCourses,
} from "./CoursewareDisplay/test-utils"
import {
CourseWithCourseRunsSerializerV2,
Expand All @@ -26,6 +27,11 @@ import { useFeatureFlagEnabled } from "posthog-js/react"
import { FeatureFlags } from "@/common/feature_flags"
import { contractAdminView } from "@/common/urls"

// Verified cards look up their order; default to none, tests override.
beforeEach(() => {
setupOrderHistory()
})

jest.mock("posthog-js/react", () => ({
...jest.requireActual("posthog-js/react"),
useFeatureFlagEnabled: jest.fn(),
Expand Down
Loading
Loading