Enrollment / Discount Codes
Overview
The ability to generate enrollment and discount codes for courses currently exists on edX white label sites with the use of the shoppingcart module. There is now the need to transition this functionality to Otto (the ecommerce module currently used on edx.org).
The requirements for this work have been outlined in Enrollment & Discount Codes on edX.org v1.0. The new requirements are far greater than what is currently implemented in the white label offering so we will use a phased approach to accomplish all of the goals.
Since Otto currently sits atop django oscar; basket, offers, vouchers etc. are already supported. The work is to enhance the built-in functionality with specifics to meet the new requirements.
This document will outline the technical approach to enable these features.
A list of use cases has also been outlined and the following approach satisfies them as well as sets us up to enhance the functionality to support financial aid as well as gift cards in the future.
Proposal
Phase 1:
Add the ability to create enrollment / discount codes for a course or courses
Add the ability to redeem and enroll in a course via a unique URL
Add the ability to send enrollment codes to clients.
Add the ability to generate reports on enrollment codes
Design
Enrollment codes are to be implemented as a new product. This will set us up for the future when we want learners to buy codes directly as a self service model. In order to support this the following new models will be created.
Enrollment Code Creation
Establish a new product class. An order will be created with line items of this class. This class can handle a catalog of courses and if a single course is chosen a catalog will be created with the single course.
class Migration(migrations.Migration): dependencies = [ ('catalogue', '0010_catalog'), ] operations = [ migrations.AddField( model_name='catalog', name='enrollment_code', field=models.ForeignKey(related_name='enrollment_codes', blank=True, to='catalogue.ProductClass', null=True), ), ]
This new class will allow us to attach a client to an order of enrollment codes. We create this new class to separate clients from self service users.
class Client(core.User): (an oscar user is a django user) pass
Enrollment Code Invoice Payment Processor
A new payment processor class will be created. This processor will record the fact that an invoice was issued for payment to fulfill the enrollment code(s) purchase. By using this class we can process a zero dollar order and kick off fulfillment. In the future this class can be made to interface with salesforce or whatever financial system we use to capture client payments.
class Invoice(BasePaymentProcessor):
Enrollment Code Fulfillment
A new fulfillment module will be created that will be responsible for creating enrollment codes for each line item in the order. We will create a new model to hold the codes.
class EnrollmentCode(models.Model): date_created = models.DateField(auto_now_add=True) voucher[] = ManyToMany(Voucher.voucher) order_line_id = ForeignKey(Line.line)
We need to extend the default Range of products to allow for a link to a catalog that will be created for the chosen courses. A Range is used to create an oscar Voucher that applies to a range of products.
class Range(AbstractRange): catalog_id = ForeignKey(Catalog.catalog)
Enrollment Code List View
Since enrollment codes are actual orders, the list view will be sorted by clients orders. The order details could then be viewed and therefore all the enrollment codes for that order would be displayed.
Discount Code Creation
SInce Discount Codes are not a product we will create them right from the management UI.
Code Redemption
Before a redesigned checkout page is in place that allows a user to apply codes to a basket, code redemption will be via a unique URL.
NOTE: We need official UX design flows for this.
Implementation
Enrollment code creation will be implemented with the following steps:
Build product (SKU), if not already created
This will be built by the user choosing a course or list of courses
a matching catalog will be created
Add product lines to a basket and create an order
Checkout the order, setting the payment method to invoice, and allowing fulfillment to continue.
Fulfill the order (via fulfillment module)
Create vouchers for each line item in the order
Enrollment code redemption will be implemented in the following steps:
Look up Voucher id
Display the matching courses to the user
Create an order for the course selected applying the voucher to the order (basket)
Then use the existing fulfillment module to enroll the user in the course
Discount Code creation will be implemented in the following steps:
Validate discount code fields.
Create a catalog for selected courses
Create Client (if not already created)
Create a DiscountCode object
Create voucher of the desired discount
Endpoints
The following endpoints will be implemented: These endpoints will be implemented using DRF.
url(r'^/actions/create_enrollment_code_order/$', views.EnrollmentCodeCreateView.as_view(), name='create_enrollment_code_order')
This endpoint is for creating new enrollment codes. The payload is a client id along with the courses that this code is associated with. The details of the code are also included, namely the dates and where or not the code is single or multi use and the number of codes to generate.
Example:
POST Body: data = { 'client_id': 1, 'stock_record_id': [1,2,3,4], 'quantity': 10, 'start_date': “2015-01-01T00:00:00Z”, 'end_date': “2015-01-01T00:00:00Z”, 'type': multiple } response: 200 Success response.json {"order_id" : "EDX-1234"} 400 Error in data provided 401 Unauthorized 403 No Permissions
In order to redeem an enrollment code.
url(r'^enrollment_codes/<code>/redeem/$', 'register_code_redemption', name='register_code_redemption')
GET Body: data = Empty response: 200 code redeemed response.json same as above response 400 Error in data provided
The endpoint for creating a discount code will be an action that takes the details needed for a code. Creation of the discount code will be done and fulfilled immediately since this is not an order.
url(r'^/actions/create_discount_code/$', views.DiscountCodeCreateView.as_view(), name='create_discount_code')
Example:
POST Body: data = { 'client_id': 1, 'stock_record_id': [1,2,3,4], 'start_date': “2015-01-01T00:00:00Z”, 'end_date': “2015-01-01T00:00:00Z”, 'type': multiple ‘fixed’: true | false ‘amount’ : amount ‘code’ : code } response: 200 Success response.json {"discount code" : "EDX-1234"} 400 Error in data provided 401 Unauthorized 403 No Permissions
GET Body: data = { 'code': EDX-1234} response: 200 code details response.json { 'client_id': 1, 'stock_record_id': [1,2,3,4], 'start_date': “2015-01-01T00:00:00Z”, 'end_date': “2015-01-01T00:00:00Z”, 'type': multiple ‘fixed’: true | false ‘amount’ : amount ‘code’ : code } 400 Error in data provided 401 Unauthorized 403 No Permissions
Endpoint to return the list of clients:
url(r'^/clients/$', views.ClientView.as_view(), name=”clients”)
GET Body: data = Empty Response: 200 Clients response.json data = { ‘clients’: [{“name”: client, “email”: email}, {“name”: client, “email”: email},] } 400 Error in data provided 401 Unauthorized
GET Body: data = { “client” : client} Response: 200 Client response.json data = { “name”: client, ‘email’: email, } 400 Error in data provided 401 Unauthorized