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.


Enrollment Codes - 1.jpg


Product Class
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.  

 

Client
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.

 

EnrollmentCode
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.  


Enrollment Codes - 2.jpg


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:

  1. Build product (SKU), if not already created

    1. This will be built by the user choosing a course or list of courses

    2. a matching catalog will be created

  2. Add product lines to a basket and create an order

  3. Checkout the order, setting the payment method to invoice, and allowing fulfillment to continue.

  4. Fulfill the order (via fulfillment module)

    1. Create vouchers for each line item in the order


Enrollment code redemption will be implemented in the following steps:

  1. Look up Voucher id

  2. Display the matching courses to the user

  3. Create an order for the course selected applying the voucher to the order (basket)

  4. Then use the existing fulfillment module to enroll the user in the course


Discount Code creation will be implemented in the following steps:

  1. Validate discount code fields.

  2. Create a catalog for selected courses

  3. Create Client (if not already created)

  4. Create a DiscountCode object

  5. 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