Commit 59fa5e83 by Ahsan Ulhaq

Credit Payment - Email Receipt

ECOM-1796
parent 2aed68c9
import logging
import analytics import analytics
import waffle
from django.dispatch import receiver from django.dispatch import receiver
from oscar.core.loading import get_class import waffle
from ecommerce.courses.utils import mode_for_seat from ecommerce.courses.utils import mode_for_seat
from ecommerce.extensions.analytics.utils import is_segment_configured, parse_tracking_context, log_exceptions from ecommerce.extensions.analytics.utils import is_segment_configured, parse_tracking_context, log_exceptions
from ecommerce.extensions.checkout.utils import get_provider_data
from ecommerce.notifications.notifications import send_notification from ecommerce.notifications.notifications import send_notification
from ecommerce.settings.base import get_lms_url
from oscar.core.loading import get_class
post_checkout = get_class('checkout.signals', 'post_checkout') post_checkout = get_class('checkout.signals', 'post_checkout')
logger = logging.getLogger(__name__)
# Number of orders currently supported for the email notifications
ORDER_LINE_COUNT = 1
@receiver(post_checkout, dispatch_uid='tracking.post_checkout_callback') @receiver(post_checkout, dispatch_uid='tracking.post_checkout_callback')
...@@ -55,6 +62,30 @@ def track_completed_order(sender, order=None, **kwargs): # pylint: disable=unus ...@@ -55,6 +62,30 @@ def track_completed_order(sender, order=None, **kwargs): # pylint: disable=unus
def send_course_purchase_email(sender, order=None, **kwargs): # pylint: disable=unused-argument def send_course_purchase_email(sender, order=None, **kwargs): # pylint: disable=unused-argument
"""Send course purchase notification email when a course is purchased.""" """Send course purchase notification email when a course is purchased."""
if waffle.switch_is_active('ENABLE_NOTIFICATIONS'): if waffle.switch_is_active('ENABLE_NOTIFICATIONS'):
# Here we assume that order type will always be a 'Seat' i.e. course # We do not currently support email sending for orders with more than one item.
if len(order.lines.all()) == 1: if len(order.lines.all()) == ORDER_LINE_COUNT:
send_notification(order.user, 'COURSE_PURCHASED', {'course_title': order.lines.first().product.title}) product = order.lines.first().product
provider_id = getattr(product.attr, 'credit_provider', None)
if not provider_id:
logger.error(
'Failed to send credit receipt notification. Credit seat product [%s] has not provider.', product.id
)
return
elif product.get_product_class().name == 'Seat':
provider_data = get_provider_data(provider_id)
if provider_data:
send_notification(
order.user,
'CREDIT_RECEIPT',
{
'course_title': product.title,
'receipt_page_url': get_lms_url(
'/commerce/checkout/receipt/?basket_id={}'.format(order.basket.id)
),
'credit_hours': product.attr.credit_hours,
'credit_provider': provider_data['display_name'],
}
)
else:
logger.info('Currently support receipt emails for order with one item.')
import httpretty
from django.conf import settings
from django.core import mail
from django.test import TestCase
from waffle import Switch
from ecommerce.courses.models import Course
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.extensions.checkout.signals import send_course_purchase_email
from ecommerce.settings.base import get_lms_url
from oscar.test import factories
from oscar.test.newfactories import BasketFactory, UserFactory
class SignalTests(CourseCatalogTestMixin, TestCase):
@httpretty.activate
def test_post_checkout_callback(self):
"""
When the post_checkout signal is emitted, the receiver should attempt
to fulfill the newly-placed order and send receipt email.
"""
httpretty.register_uri(
httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'),
body='{"display_name": "Hogwarts"}',
content_type="application/json"
)
Switch.objects.get_or_create(name='ENABLE_NOTIFICATIONS', active=True)
user = UserFactory()
course = Course.objects.create(id='edX/DemoX/Demo_Course', name='Demo Course')
seat = course.create_or_update_seat('credit', False, 50, 'ASU', None, 2)
basket = BasketFactory()
basket.add_product(seat, 1)
order = factories.create_order(number=1, basket=basket, user=user)
send_course_purchase_email(None, order=order)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'Order Receipt')
self.assertEqual(
mail.outbox[0].body,
'\nReceipt Confirmation for: {course_name}'
'\n\nHi {full_name},\n\n'
'Thank you for purchasing {credit_hour} credit hours from {provider_name} for {course_name}.'
' The charge below will appear on your next credit or debit card statement with a '
'company name of {platform_name}.\n\nYou can see the status the status of your credit request or '
'complete the credit request process on your {platform_name} dashboard\nTo browse other '
'credit-eligible courses visit the edX website. More courses are added all the time.\n\n'
'Thank you and congratulation on your achievement. We hope you enjoy the course!\n\n'
'To view receipt please visit the link below'
'\n\n{receipt_url}\n\n'
'{platform_name} team\n\nThe edX team\n'.format(
course_name=order.lines.first().product.title,
full_name=user.get_full_name(),
credit_hour=2,
provider_name='Hogwarts',
platform_name=settings.PLATFORM_NAME,
receipt_url=get_lms_url('/commerce/checkout/receipt/?basket_id={}'.format(order.basket.id))
)
)
import httpretty
from django.test import TestCase
from ecommerce.settings.base import get_lms_url
from ecommerce.extensions.checkout.utils import get_provider_data
class UtilTests(TestCase):
@httpretty.activate
def test_get_provider_data(self):
"""
Check if correct data returns on the full filled request.
"""
httpretty.register_uri(
httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'),
body='{"display_name": "Arizona State University"}',
content_type="application/json"
)
provider_data = get_provider_data('ASU')
self.assertDictEqual(provider_data, {"display_name": "Arizona State University"})
@httpretty.activate
def test_get_provider_data_unavailable_request(self):
"""
Check if None return on the bad request
"""
httpretty.register_uri(
httpretty.GET, get_lms_url('api/credit/v1/providers/ABC'),
status=400
)
provider_data = get_provider_data('ABC')
self.assertEqual(provider_data, None)
import logging
import requests
from django.conf import settings
from ecommerce.settings.base import get_lms_url
logger = logging.getLogger(__name__)
def get_provider_data(provider_id):
"""Get the provider information for provider id provider.
Args:
provider_id(str): Identifier for the provider
Returns: dict
"""
provider_info_url = get_lms_url('api/credit/v1/providers/{}'.format(provider_id))
timeout = settings.PROVIDER_DATA_PROCESSING_TIMEOUT
headers = {
'Content-Type': 'application/json',
'X-Edx-Api-Key': settings.EDX_API_KEY
}
try:
response = requests.get(provider_info_url, headers=headers, timeout=timeout)
if response.status_code == 200:
return response.json()
else:
logger.error(
'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s',
provider_id, response.status_code, response.text)
return None
except requests.exceptions.ConnectionError:
logger.exception('Connection error occurred during getting data for %s provider', provider_id)
return None
except requests.Timeout:
logger.exception('Failed to retrieve data for %s provider, connection timeout', provider_id)
return None
...@@ -236,6 +236,9 @@ ENROLLMENT_API_URL = None ...@@ -236,6 +236,9 @@ ENROLLMENT_API_URL = None
COMMERCE_API_TIMEOUT = 7 COMMERCE_API_TIMEOUT = 7
COMMERCE_API_URL = None COMMERCE_API_URL = None
# PROVIDER DATA PROCESSING
PROVIDER_DATA_PROCESSING_TIMEOUT = 15 # Value is in seconds.
# OAuth2 provider URL used for OAuth2 transactions (e.g. validating access tokens) # OAuth2 provider URL used for OAuth2 transactions (e.g. validating access tokens)
OAUTH2_PROVIDER_URL = None OAUTH2_PROVIDER_URL = None
# END URL CONFIGURATION # END URL CONFIGURATION
......
...@@ -11,13 +11,53 @@ ...@@ -11,13 +11,53 @@
.ReadMsgBody { width: 100%; background-color: #ebebeb;} .ReadMsgBody { width: 100%; background-color: #ebebeb;}
.ExternalClass { width: 100%; background-color: #ebebeb;} .ExternalClass { width: 100%; background-color: #ebebeb;}
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height:100%;} .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height:100%;}
body {-webkit-text-size-adjust:none; body {
-ms-text-size-adjust:none;
margin:0; margin:0;
padding:10px 0; padding:10px 0;
} }
table {border-spacing:0;} table {border-spacing:0;}
table td {border-collapse:collapse;} table td {border-collapse:collapse;}
.container-pre-header {
background-color: #ffffff;
padding-left: 30px;
padding-right: 30px;
font-size: 14px;
line-height: 20px;
font-family: Open Sans, sans-serif;
color: #333;
border-top: 10px solid #1AA1E0;
border-radius: 5px;
}
.container-header {
padding-left: 30px;
padding-right: 30px;
font-size: 14px;
line-height: 20px;
font-family: Open Sans, sans-serif;
color: #333;
background-color: #f3f3f3;
border-top: 1px solid #cccccc;
border-bottom: 1px solid #eeeeee;
}
.container-body{
background-color: #ffffff;
padding-left: 30px;
padding-right: 30px;
font-size: 13px;
line-height: 20px;
font-family: Open Sans, sans-serif;
color: #333;
border-radius: 5px;
}
.container-footer{
padding-left: 30px;
padding-right: 30px;
font-size: 13px;
line-height: 20px;
font-family: Open Sans, sans-serif;
color: #002D40;
background-color: #f3f3f3;
}
.yshortcuts a {border-bottom: none !important;} .yshortcuts a {border-bottom: none !important;}
/* Constrain email width for small screens */ /* Constrain email width for small screens */
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
...@@ -86,80 +126,9 @@ ...@@ -86,80 +126,9 @@
<td align="center" valign="top" bgcolor="#dddddd" style="background-color: #dddddd;"> <td align="center" valign="top" bgcolor="#dddddd" style="background-color: #dddddd;">
<!-- 600px container (white background) --> <!-- 600px container (white background) -->
<table border="0" width="600" cellpadding="0" cellspacing="0" class="container" bgcolor="#ffffff" style="border-radius: 5px; box-shadow: 0 0 2px #aaaaaa;"> <table border="0" width="600" cellpadding="0" cellspacing="0" class="container" bgcolor="#ffffff" style="border-radius: 5px; box-shadow: 0 0 2px #aaaaaa;">
<tr>
<td class="container-padding" bgcolor="#ffffff" style="background-color: #ffffff; padding-left: 30px; padding-right: 30px; font-size: 14px; line-height: 20px; font-family: Open Sans, sans-serif; color: #333; border-top: 10px solid #1AA1E0; border-radius: 5px;">
<table border="0" cellpadding="0" cellspacing="0" class="pre-header-container">
<tr>
<td align="left" valign="middle" style="font-size:30px; font-style: bold; height: 50px; font-family: Open Sans, Arial, sans-serif;">
<img src="http://gallery.mailchimp.com/1822a33c054dc20e223ca40e2/images/logo_web_edx_studio01aad778f34f.png" height="30" alt="{platform_name} Logo" border="0" hspace="0" vspace="0" class="platform-img">
</td>
<td align="left" class="email-type" valign="top" style="font-size: 20px; vertical-align: middle; padding-left: 20px; font-family: Open Sans, Arial, sans-serif; color: #127eb1;">
{% trans "Receipt Confirmation" %}
</td>
</tr>
</table><!--/ end .pre-header-container-->
</td>
</tr>
<!-- Header -->
{% block header %}
<tr>
<td class="container-padding" bgcolor="#f3f3f3" style="padding-left: 30px; padding-right: 30px; font-size: 14px; line-height: 20px; font-family: Open Sans, sans-serif; color: #333; background-color: #f3f3f3; border-top: 1px solid #cccccc; border-bottom: 1px solid #eeeeee;">
<table border="0" cellpadding="0" cellspacing="0" class="columns-container" style="width:100%;">
<tr>
<td class="force-col" style="padding-right: 20px;" valign="middle">
<!-- ### COLUMN 1 - COURSE AND TITLE ### -->
<table border="0" cellspacing="0" cellpadding="0" width="280" align="left" class="col-2">
<tr>
<td align="left" valign="top" style="font-size:13px; line-height: 20px; font-family: Open Sans, Arial, sans-serif; color: #aaaaaa;">
<br>
{% trans "Receipt Confirmation for:" %}
</td>
</tr>
<tr>
<td align="left" valign="top" style="line-height: 26px; font-family: Open Sans, Arial, sans-serif; font-style: bold; font-size:24px; color: #002D40;">
{{course_title}}
<br><br>
</td>
</tr>
</table>
</td>
<td class="force-col" valign="middle">
<!-- ### COLUMN 2 - VIEW RECEIPT ### -->
<table border="0" cellspacing="0" cellpadding="10" width="100" align="right" class="col-2">
<tr>
<td align="center" valign="middle" style="font-size:13px; font-family: Open Sans, Arial, sans-serif; background-color: #127eb1; border-radius: 5px; box-shadow: 0 2px #002D40;">
<a href="{{course_url}}" style="color: #ffffff; text-decoration: none;">{% trans "View Receipt" %}</a>
</td>
</tr>
</table>
</td>
</tr>
</table><!--/ end .columns-container-->
</td>
</tr>
{% endblock header %}
{% block body %} {% block body %}
{% endblock body %} {% endblock body %}
{% block footer %} {% block footer %}
<tr>
<td class="footer-padding" style="padding-left: 30px; padding-right: 30px; padding-bottom: 20px; font-size: 13px; line-height: 20px; font-family: Open Sans, sans-serif; color: #002D40; border-radius: 5px;" align="left">
<table border="0" cellpadding="0" cellspacing="0" class="columns-container">
<tr>
<td class="force-col" style="padding-right: 20px;" valign="middle">
<!-- ### COLUMN 1 - FOOTER ### -->
<table border="0" cellspacing="0" cellpadding="10" width="100" align="right" class="col-2-actions">
<tr>
<td align="center" valign="right" style="font-size:13px; font-family: Open Sans, Arial, sans-serif; background-color: #127eb1; border-radius: 5px; box-shadow: 0 2px #002D40;">
<a href="{{course_url}}" style="color: #ffffff; text-decoration: none;">{% trans "View Receipt" %}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<br>
</td>
</tr>
<!-- Copyright and Address --> <!-- Copyright and Address -->
<tr> <tr>
<td class="container-padding" bgcolor="#ffffff" style="padding-left: 30px; padding-right: 30px; font-size: 14px; line-height: 20px; font-family: Open Sans, sans-serif; color: #333; background-color: #ffffff; border-bottom: 1px solid #cccccc; border-radius: 5px;"> <td class="container-padding" bgcolor="#ffffff" style="padding-left: 30px; padding-right: 30px; font-size: 14px; line-height: 20px; font-family: Open Sans, sans-serif; color: #333; background-color: #ffffff; border-bottom: 1px solid #cccccc; border-radius: 5px;">
......
{% load i18n %} {% load i18n %}
{% trans "Receipt Confirmation for: " %}{{notification_data.course_title}} {% trans "Receipt Confirmation for: " %}{{course_title}}
{% blocktrans %}Hi {{full_name}},{% endblocktrans %} {% blocktrans %}Hi {{full_name}},{% endblocktrans %}
......
{% extends 'customer/email_base.html' %}
{% load i18n %}
{% block body %}
<tr>
<td class="container-padding container-pre-header" bgcolor="#ffffff">
<table border="0" cellpadding="0" cellspacing="0" class="pre-header-container">
<tr>
<td align="left" valign="middle" style="font-size:30px; font-style: bold; height: 50px; font-family: Open Sans, Arial, sans-serif;">
<img src="http://gallery.mailchimp.com/1822a33c054dc20e223ca40e2/images/logo_web_edx_studio01aad778f34f.png" height="30" alt="{platform_name} Logo" border="0" hspace="0" vspace="0" class="platform-img">
</td>
<td align="left" class="email-type" valign="top" style="font-size: 20px; vertical-align: middle; padding-left: 20px; font-family: Open Sans, Arial, sans-serif; color: #127eb1;">
{% trans "Receipt Confirmation" %}
</td>
</tr>
</table><!--/ end .pre-header-container-->
</td>
</tr>
<tr>
<td class="container-padding container-header" bgcolor="#f3f3f3">
<table border="0" cellpadding="0" cellspacing="0" class="columns-container" style="width:100%;">
<tr>
<td class="force-col" style="padding-right: 20px;" valign="middle">
<!-- ### COLUMN 1 - COURSE AND TITLE ### -->
<table border="0" cellspacing="0" cellpadding="0" width="280" align="left" class="col-2">
<tr>
<td align="left" valign="top" style="font-size:13px; line-height: 20px; font-family: Open Sans, Arial, sans-serif; color: #aaaaaa;">
<br>
{% trans "Receipt Confirmation for:" %}
</td>
</tr>
<tr>
<td align="left" valign="top" style="line-height: 26px; font-family: Open Sans, Arial, sans-serif; font-style: bold; font-size:24px; color: #002D40;">
{{course_title}}
<br><br>
</td>
</tr>
</table>
</td>
<td class="force-col" valign="middle">
<!-- ### COLUMN 2 - VIEW RECEIPT ### -->
<table border="0" cellspacing="0" cellpadding="10" width="100" align="right" class="col-2">
<tr>
<td align="center" valign="middle" style="font-size:13px; font-family: Open Sans, Arial, sans-serif; background-color: #127eb1; border-radius: 5px; box-shadow: 0 2px #002D40;">
<a href="{{receipt_page_url}}" style="color: #ffffff; text-decoration: none;">{% trans "View Receipt" %}</a>
</td>
</tr>
</table>
</td>
</tr>
</table><!--/ end .columns-container-->
</td>
</tr>
<!-- Message Body -->
<tr>
<td class="container-padding container-body" bgcolor="#ffffff" align="left">
<br>
<!--/message HTML content -->
<p>{% blocktrans %}Hi {{full_name}},{% endblocktrans %}</p>
<br>
<p>{% blocktrans with credit_hours=credit_hours credit_provider=credit_provider course_title=course_title platform_name=platform_name %}Thank you for purchasing {{credit_hours}} credit hours from {{credit_provider}} for {{course_title}}. The charge below will appear on your next credit or debit card statement with a company name of {{platform_name}}.{% endblocktrans %}</p>
<br>
<p>{% blocktrans %}You can see the status the status of your credit request or complete the credit request process on your {{platform_name}} dashboard{% endblocktrans %}</p>
<p>{% blocktrans %}To browse other credit-eligible courses visit the {{platform_name}} website. More courses are added all the time.{% endblocktrans %}</p>
<br>
<p>{% trans "Thank you and congratulation on your achievement. We hope you enjoy the course!" %}</p>
<p>{% blocktrans %}{{platform_name}} team{% endblocktrans %}</p>
<!--/message HTML content -->
<br><br>
</td>
</tr>
<tr>
<td class="footer-padding container-footer" align="left">
<!--footer content -->
<br>
<p>{% blocktrans with course_title=course_title platform_name=platform_name %}You are receiving this email because you purchased a seat in the {{platform_name}} course {{course_title}}.{% endblocktrans %}</p>
<br>
<!--/ end footer content -->
</td>
</tr>
<!--/100% wrapper-->
<br>
<br>
{% endblock body %}
{% load i18n %}
{% trans "Receipt Confirmation for: " %}{{course_title}}
{% blocktrans %}Hi {{full_name}},{% endblocktrans %}
{% blocktrans with credit_hours=credit_hours credit_provider=credit_provider course_title=course_title platform_name=platform_name %}Thank you for purchasing {{credit_hours}} credit hours from {{credit_provider}} for {{course_title}}. The charge below will appear on your next credit or debit card statement with a company name of {{platform_name}}.{% endblocktrans %}
{% blocktrans %}You can see the status the status of your credit request or complete the credit request process on your {{platform_name}} dashboard{% endblocktrans %}
{% blocktrans %}To browse other credit-eligible courses visit the edX website. More courses are added all the time.{% endblocktrans %}
{% trans "Thank you and congratulation on your achievement. We hope you enjoy the course!" %}
{% trans "To view receipt please visit the link below" %}
{{receipt_page_url}}
{% blocktrans %}{{platform_name}} team{% endblocktrans %}
{% trans "The edX team" %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment