Skip to content

anonymous checkout - #3709

Open
gumaerc wants to merge 6 commits into
update-mitxonline-api-clientfrom
cg/anonymous-checkout
Open

anonymous checkout#3709
gumaerc wants to merge 6 commits into
update-mitxonline-api-clientfrom
cg/anonymous-checkout

Conversation

@gumaerc

@gumaerc gumaerc commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

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

Description (What does it do?)

Lets a logged-out learner start a paid purchase without creating an account first. Behind the anonymous-checkout feature flag, clicking a paid enroll CTA while unauthenticated now hands off directly to the MITx Online basket/checkout instead of showing the signup popover; account creation happens later in the MITx Online flow (Review → Account → Verify → Payment).

  • Feature flag AnonymousCheckout (anonymous-checkout), off by default → no behavior change. With the flag off, an unauthenticated paid (or free) click still routes to onRequireSignup, exactly as today.
  • Enroll hooks (useCourseEnrollment, useProgramEnrollment): the auth guard is now !isAuthenticated && (kind === "free" || !anonymousCheckoutEnabled) → onRequireSignup. With the flag on, an unauthenticated paid click proceeds to the basket handoff; free enrollment still requires an account (there is no anonymous audit/free enrollment).
  • useReplaceBasketItem: threads the created basket's anonymous_id into the /cart/ redirect as an anonymous_basket_id query param. Django's session cookie for the anonymous basket is host-only and can't cross from Learn's proxied API host to MITx Online's own domain, so the id is handed off explicitly in the URL (MITx Online's AnonymousBasketHandoffMiddleware adopts it into the session and strips it).
  • Basket mutation hooks (useAddToBasket / useClearBasket): skip basketState query invalidation when unauthenticated. basketState hits the checkout-payload endpoint, which creates a real order against the basket's purchaser; an anonymous basket has no purchaser, so invalidation must wait until after the anonymous→authenticated conversion.
  • Adds enroll / begin_checkout analytics on the paid path.

How can this be tested?

  • Make sure you have mitxonline running and connected to Learn as described in the readme
  • Make sure your mitxonline data includes a purchasable program or course
  • Make sure you have the Cybersource credentials from RC loaded into your local, which should be all keys prefixed with MITOL_PAYMENT_GATEWAY_CYBERSOURCE_ as well as ECOMMERCE_DEFAULT_PAYMENT_GATEWAY="Cybersource"
  • Make sure you have Posthog connected and have the anonymous-checkout feature flag enabled
  • Install a temporary branch client of the @mitodl/mitxonline-api-axios package with docker compose exec yarn up @mitodl/mitxonline-api-axios@https://github.com/mitodl/mitxonline-api-clients/raw/954216380a8d7f09f9b7bfa00c09d09b28bd4cfe/src/typescript/mitxonline-api-axios/package.tgz
  • Open an incognito window and browse to the product page for the purchasable program / course you set up
  • Click the Enroll in Program / Course
  • Verify you are sent to the cart page
  • Click Place your order
  • You should be sent to Keycloak login, where you should create an account or log in to an existing one (best to test both paths)
  • Once you have verified your email address, you should be sent to Cybersource for payment info
  • Enter fake / test payment info and checkout
  • Verify that you are sent to onboarding in Learn
  • After going through onboarding, verify that you are sent to the dashboard and you see the Program / Course that you purchased

Additional Context

  • Temporary API client pin. @mitodl/mitxonline-api-axios is pinned to a branch build of the pending MITx Online release (for anonymous_id and the new basket types). This must be swapped for the official published version once that release ships.
  • Bundled type change. That pending release also carries the b2b sso_organization_id field (MITx Online #3807), which is required on OrganizationPage. Adopting the client therefore also requires a small test-fixture update for the org factory/tests — coupled to the client bump, not to anonymous-checkout behavior. (Runtime already tolerates it via orgUuid.ts, added earlier in feat(b2b): org analytics dashboard for the manager surface #3679.)
  • The feature flag makes this safe to merge ahead of a coordinated launch — the reordered flow only activates when the flag is turned on.

Checklist:

  • Swap the temporary @mitodl/mitxonline-api-axios pin for the official release build before/at release
  • Confirm the anonymous-checkout PostHog flag exists in each environment

Copilot AI review requested due to automatic review settings July 31, 2026 20:50
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

OpenAPI Changes

No changes detected

View full changelog

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR begins enabling anonymous paid checkout flows by handing unauthenticated users off to MITx Online’s basket/cart flow (gated behind a PostHog feature flag) and ensuring the anonymous basket identifier is preserved across the redirect.

Changes:

  • Add anonymous-checkout feature flag and gate paid enrollment handoff behavior in course/program enrollment hooks.
  • Update basket/cart redirect logic to append anonymous_basket_id when applicable, and adjust React Query invalidation to avoid hitting checkout payload for anonymous baskets.
  • Update/extend frontend tests to account for the new auth/flag behavior and additional Learn-auth mocking.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
yarn.lock Updates lock entries to pull @mitodl/mitxonline-api-axios from a GitHub tarball URL.
frontends/main/src/page-components/EnrollmentDialogs/CourseEnrollmentDialog.test.tsx Mocks Learn auth state needed by updated basket hooks.
frontends/main/src/common/mitxonline/useReplaceBasketItem.ts Appends anonymous_basket_id to MITx Online cart redirect URL.
frontends/main/src/common/mitxonline/useReplaceBasketItem.test.tsx Adds tests validating anonymous basket redirect query param behavior.
frontends/main/src/common/feature_flags.ts Adds AnonymousCheckout flag constant.
frontends/main/src/app-pages/ProductPages/useProgramEnrollment.ts Gates anonymous paid handoff behavior on feature flag.
frontends/main/src/app-pages/ProductPages/useProgramEnrollment.test.tsx Tests paid unauthenticated behavior with flag on/off.
frontends/main/src/app-pages/ProductPages/useCourseEnrollment.ts Gates anonymous paid handoff behavior on feature flag.
frontends/main/src/app-pages/ProductPages/useCourseEnrollment.test.tsx Tests paid unauthenticated behavior with flag on/off.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/*.test.tsx Adds Learn-auth mocking required by basket hook changes.
frontends/main/package.json Switches @mitodl/mitxonline-api-axios dependency to a GitHub tarball URL.
frontends/api/src/mitxonline/hooks/baskets/index.ts Avoids invalidating checkout-payload query for anonymous baskets by checking Learn auth state.
frontends/api/package.json Switches @mitodl/mitxonline-api-axios dependency to a GitHub tarball URL.
Suppressed comments (1)

frontends/main/src/common/mitxonline/useReplaceBasketItem.test.tsx:138

  • Same as the earlier test: this mockImplementationOnce override re-types opts.onSuccess to accept a basket argument, which may be incompatible with the original mutate mock type and fail type-checking. Loosen/cast the override types so the mock remains assignable.
    mutate.mockImplementationOnce(
      (_productId: number, opts?: { onSuccess?: (b: unknown) => void }) =>
        opts?.onSuccess?.({ id: 9, user: 1, anonymous_id: null }),
    )

Comment thread frontends/main/package.json Outdated
"@mitodl/course-search-utils": "^3.5.2",
"@mitodl/hacksnack": "^0.1.0",
"@mitodl/mitxonline-api-axios": "2026.7.22",
"@mitodl/mitxonline-api-axios": "https://github.com/mitodl/mitxonline-api-clients/raw/c6157f4f8c6991b080a76d3d140e6114076aeab8/src/typescript/mitxonline-api-axios/package.tgz",
Comment thread frontends/api/package.json Outdated
"dependencies": {
"@mitodl/mit-learn-api-axios": "2026.7.22",
"@mitodl/mitxonline-api-axios": "2026.7.22",
"@mitodl/mitxonline-api-axios": "https://github.com/mitodl/mitxonline-api-clients/raw/c6157f4f8c6991b080a76d3d140e6114076aeab8/src/typescript/mitxonline-api-axios/package.tgz",
Comment thread frontends/main/src/common/mitxonline/useReplaceBasketItem.test.tsx Outdated
@gumaerc
gumaerc force-pushed the cg/anonymous-checkout branch 5 times, most recently from 85fdc58 to 1487741 Compare August 5, 2026 17:54
@gumaerc gumaerc changed the title [WIP] anonymous checkout anonymous checkout Aug 5, 2026
@ChristopherChudzicki ChristopherChudzicki self-assigned this Aug 5, 2026

@ChristopherChudzicki ChristopherChudzicki left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Working well, I am going to make a few changes, primarily (1) and (2) below.

  1. Revert the auth check on the basket invalidations — restore the unconditional invalidateQueries in useAddToBasket and useClearBasket.[^1]
    • Nothing should be subscribe without authentication: UI either doesn't load that, or it would be gated by enabled.
    • If something actually is subscribed, it should be invalidated.
    • Aside: Nothing actually uses that query right now at all.
    • Plus the gating as-is is on Learn session; if it's gated at all, it should be gated on MITxOnline session
  2. Test what the PR actually does — add a test that feeds a basket carrying an anonymous_id through the real useReplaceBasketItem and asserts the redirect URL has ?anonymous_basket_id=. Nothing covers that seam today.[^3]
  3. Share pending state across basket mutation instancesCoursePage.tsx:52 and CourseEnrollArea.tsx:22 each own a separate useReplaceBasketItem and each disables only on its own pending state, so clicking one CTA then the other starts two clear→add chains.[^2] CourseEnrollmentDialog.tsx:271 needs the same treatment; it's disabled={!enabled} today, so a plain double-click does it.
  4. Add isError to useEnrollmentHandler — it returns isPending but not isError (lines 175–181), and UnenrolledCourseCard.tsx:51 consumes only isPending, so a failed dashboard upgrade shows the user nothing at all.

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.

3 participants