E-Commerce IDA (Otto)

Repos 

https://github.com/edx/ecommerce and https://github.com/edx/ecommerce-worker

Business Purpose

We use a Django application called ecommerce to manage the edX product catalog and handle orders for those products. The ecommerce service extends Oscar, an open source Django ecommerce framework, and hosts internal tools for configuring courses and coupons.

Data

The service is a source of truth for the following data:

Products

As of this writing, these include seats and coupons. Seats, associated with an ecommerce-specific Course model, are abstractions on top of the LMS’ enrollment modes. Coupons are an abstraction on top of Oscar’s Voucher app used to represent benefits such as discounts.

Stock Records

Product variant information. For example, a seat in a course may have “audit” and “verified” variants, each with its own pricing information.

Catalogs

Product groupings, used to define a set of products to which an offer applies.

Offers

Rules used to determine if a basket qualifies for a benefit (e.g., a discount).

Baskets

User-specific collections of products which have yet to be ordered. An order is created once the contents of a basket are paid for.

Orders

User-specific collections of products which have been paid for and the system must fulfill (e.g., by enrolling the user in a specific mode on the LMS).

Payments

The service relies on third-party services (e.g., CyberSource, PayPal) for payment processing. Records are kept of every payment processor response received by the service.

User information

LMS usernames, full names, email addresses, and optional tracking context (e.g., Google Analytics client ID). The service requires user state in order to function. Django users which mirror LMS users interacting with the application are created using a custom JWT authentication handler.

Service Interactions

See here for a high-level overview of service interactions involved in the order placement process. In more detail:

0) User’s basket is populated and frozen, initiating the checkout process.
1) If necessary, user is redirected to an external payment processor.
2) External payment processor makes a request back to the ecommerce service to signal that payment has completed.
3) Ecommerce records payment metadata, creates an order, and enqueues a task for its fulfillment.
4) Ecommerce worker pulls the task of the queue.
5) Ecommerce worker makes a request to ecommerce signaling that fulfillment for the given order should occur.
6) Ecommerce fulfills the order, making requests to the LMS if necessary (for enrollment).

AuthN/AuthZ

See here for a diagram of authentication techniques used by edX’s IDAs, including ecommerce. In particular, the ecommerce service supports session authentication, JWT-based authentication, and OAuth 2.0 access token-based authentication.

Session authentication is initiated using our central LMS-hosted OAuth 2.0 provider using the authorization code flow. Single sign-off is not currently supported. A login session can only be terminated by clicking a logout link that appears on certain pages of the browseable API or Django admin screens, or by otherwise deleting browser cookies. Session authentication facilitates data administration and diagnostics, but is also used by public audiences during checkout.

JWT authentication is supported via the PyJWT library and plugins that integrate it with DRF’s authentication framework. JWT tokens are verified using a signing key shared by the LMS and ecommerce. This is what external services use to interact with the ecommerce API. The JWT subject is the user who initiated the request at the originating system. When fulfilling orders, the JWT represents a service user account that is authorized to modify order data. (The service user account is created with an unusable password. It is only intended to be used programmatically.)

When making requests to external APIs, the ecommerce service uses OAuth 2.0 access tokens, or, in the case of the LMS-hosted Enrollment API, a symmetric API key.

The present AuthZ model is to allow users with global “admin” (staff) status to perform any read/write operations, while non-admin users may only access data that they “own” via APIs (for example, a list of orders they have placed).