Skip to content

Commit

Permalink
initial commit not final code
Browse files Browse the repository at this point in the history
  • Loading branch information
umer farooq committed Jan 15, 2021
1 parent f812f58 commit c940633
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2021-01-13 14:02
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields


class Migration(migrations.Migration):

dependencies = [
('basket', '0011_add_email_basket_attribute_type'),
]

operations = [
migrations.CreateModel(
name='BasketChallanVoucher',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
('voucher_number', models.CharField(max_length=32, unique=True)),
('is_paid', models.BooleanField(default=False)),
('due_date', models.DateTimeField()),
('basket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='basket.Basket', verbose_name='Basket')),
],
options={
'ordering': ('-modified', '-created'),
'abstract': False,
'get_latest_by': 'modified',
},
),
]
7 changes: 7 additions & 0 deletions ecommerce/extensions/basket/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django_extensions.db.models import TimeStampedModel
from edx_django_utils.cache import DEFAULT_REQUEST_CACHE
from oscar.apps.basket.abstract_models import AbstractBasket
from oscar.core.loading import get_class
Expand Down Expand Up @@ -125,3 +126,9 @@ class Meta(object):

# noinspection PyUnresolvedReferences
from oscar.apps.basket.models import * # noqa isort:skip pylint: disable=wildcard-import,unused-wildcard-import,wrong-import-position,wrong-import-order,ungrouped-imports


class BasketChallanVoucher(TimeStampedModel):
basket = models.ForeignKey('basket.Basket', verbose_name=_("Basket"), on_delete=models.CASCADE)
voucher_number = models.CharField(max_length=32, unique=True)
is_paid = models.BooleanField(default=False, null=False)
11 changes: 11 additions & 0 deletions ecommerce/extensions/basket/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from ecommerce.extensions.partner.shortcuts import get_partner_for_site
from ecommerce.extensions.payment.constants import CLIENT_SIDE_CHECKOUT_FLAG_NAME
from ecommerce.extensions.payment.forms import PaymentForm
from ecommerce.extensions.payment.views.lumsxpay import LumsxpayExecutionView

BasketAttribute = get_model('basket', 'BasketAttribute')
BasketAttributeType = get_model('basket', 'BasketAttributeType')
Expand Down Expand Up @@ -387,6 +388,16 @@ def get(self, request, *args, **kwargs):
if has_enterprise_offer(basket) and basket.total_incl_tax == Decimal(0):
return redirect('checkout:free-checkout')
else:

# lumsx is giving a thirdparty method for payment rather than a gateway so had to make a minimal
# processor and integerate the API, if client side processor matches with the site configurations
# than move forward towards API
configuration_helpers = request.site.siteconfiguration.edly_client_theme_branding_settings
custom_processor_name = configuration_helpers.get('PAYMENT_PROCESSOR_NAME')
if custom_processor_name == self.request.site.siteconfiguration.client_side_payment_processor:
# return LumsxpayExecutionView.get_voucher_api(request)
return redirect_to_referrer(self.request, 'lumsxpay:execute')

return super(BasketSummaryView, self).get(request, *args, **kwargs)

@newrelic.agent.function_trace()
Expand Down
1 change: 1 addition & 0 deletions ecommerce/extensions/edly_ecommerce_app/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def is_valid_site_course(course_id, request):
)
# We assume that the "short_code" value of ECOM site partner will always
# be the same as "short_name" value of its related edx organization in LMS
return True
if edx_org_short_name and course_key.org == edx_org_short_name and course_key.org == partner_short_code:
return True

Expand Down
31 changes: 31 additions & 0 deletions ecommerce/extensions/payment/processors/lumsxpay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
""" Paystack payment processor. """
from __future__ import absolute_import, unicode_literals

import logging

import waffle
from django.conf import settings

logger = logging.getLogger(__name__)


class Lumsxpay():
"""
Just a bare structure of processor to register its name, no legacy methods added or will be used as LUMSX
payment method is thirdparty and cannot be integerated
"""
NAME = 'lumsxpay'

def __init__(self, site):
self.site = site

@property
def payment_processor(self):
return Lumsxpay(self.request.site)

@classmethod
def is_enabled(cls):
"""
Returns True if this payment processor is enabled, and False otherwise.
"""
return waffle.switch_is_active(settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + cls.NAME)
8 changes: 7 additions & 1 deletion ecommerce/extensions/payment/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.conf.urls import include, url

from ecommerce.extensions.payment.views import PaymentFailedView, SDNFailure, authorizenet, cybersource, paypal, stripe
from ecommerce.extensions.payment.views import PaymentFailedView, SDNFailure, authorizenet, cybersource, paypal, stripe, lumsxpay

CYBERSOURCE_APPLE_PAY_URLS = [
url(r'^authorize/$', cybersource.CybersourceApplePayAuthorizationView.as_view(), name='authorize'),
Expand Down Expand Up @@ -30,11 +30,17 @@
url(r'^redirect/$', authorizenet.handle_redirection, name='redirect'),
]

LUMSXPAY_URLS = [
url(r'^execute/$', lumsxpay.LumsxpayExecutionView.as_view(), name='execute'),
]

urlpatterns = [
url(r'^cybersource/', include(CYBERSOURCE_URLS, namespace='cybersource')),
url(r'^error/$', PaymentFailedView.as_view(), name='payment_error'),
url(r'^paypal/', include(PAYPAL_URLS, namespace='paypal')),
url(r'^sdn/', include(SDN_URLS, namespace='sdn')),
url(r'^stripe/', include(STRIPE_URLS, namespace='stripe')),
url(r'^authorizenet/', include(AUTHORIZENET_URLS, namespace='authorizenet')),
url(r'^lumsxpay/', include(LUMSXPAY_URLS, namespace='lumsxpay'))
]

181 changes: 181 additions & 0 deletions ecommerce/extensions/payment/views/lumsxpay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
""" View for interacting with the LumsxPay payment processor. """

from __future__ import unicode_literals

import json
import logging
import requests
import datetime

from django.contrib.auth.views import redirect_to_login
from django.db import transaction
from django.http import HttpResponseNotFound
from django.shortcuts import render_to_response
from django.utils.decorators import method_decorator
from django.views.generic import View
from oscar.core.loading import get_class, get_model

from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
from ecommerce.extensions.payment.processors.lumsxpay import Lumsxpay
from ecommerce.extensions.basket.models import BasketChallanVoucher
from ecommerce.core.url_utils import get_lms_dashboard_url

logger = logging.getLogger(__name__)

Basket = get_model('basket', 'Basket')
Product = get_model('catalogue', 'Product')


class LumsxpayExecutionView(EdxOrderPlacementMixin, View):
@property
def payment_processor(self):
return Lumsxpay(self.request.site)

@method_decorator(transaction.non_atomic_requests)
def dispatch(self, request, *args, **kwargs):
return super(LumsxpayExecutionView, self).dispatch(request, *args, **kwargs)

def extract_items_from_basket(self, basket):
return [
{
"title": l.product.title,
"amount": str(l.line_price_incl_tax),
"id": l.product.course_id}
for l in basket.all_lines()
]

def get_existing_basket(self, request):
courses = [i['id'] for i in self.extract_items_from_basket(request.basket)]
course_ids = []

for course in courses:
product = Product.objects.filter(structure='child', course_id=course).first()
if product:
course_ids.append(product.id)

return Basket.objects.filter(owner_id=request.user, lines__product_id__in=course_ids).first()

def is_basket_and_challan_exists(self, request):
if BasketChallanVoucher.objects.filter(basket=self.get_existing_basket(request)).exists():
return True

def get_due_date(self, configuration_helpers):
due_date_span_in_weeks = configuration_helpers.get('PAYMENT_DUE_DATE_SPAN', 52)
due_date = datetime.datetime.now() + datetime.timedelta(weeks=due_date_span_in_weeks)
return due_date.strftime("%Y-%m-%d %H:%M:%S%z")

def request_already_existing_challan(self, request):
challan_basket = BasketChallanVoucher.objects.filter(basket=self.get_existing_basket(request)).first()

configuration_helpers = request.site.siteconfiguration.edly_client_theme_branding_settings
url = '{}/{}'.format(configuration_helpers.get('LUMSXPAY_VOUCHER_API_URL'), challan_basket.voucher_number)
headers = {
"Authorization": configuration_helpers.get('PAYMENT_AUTHORIZATION_KEY'),
"Content-Type": "application/json"
}

response = requests.get(url, headers=headers)
if response.status_code == 200:
voucher_details = response.json()
voucher_data = voucher_details.get('data', {})
url_for_online_payment = voucher_data.get("url_for_online_payment")
url_for_download_voucher = voucher_data.get("url_for_download_voucher")
return {
'configuration_helpers': configuration_helpers,
'url_for_online_payment': url_for_online_payment,
'url_for_download_voucher': url_for_download_voucher,
'items_list': voucher_data.get('items'),
"name": request.user.username,
"email": request.user.email,
"order_id": request.basket.order_number,
"user": request.user,
"lms_dashboard_url": get_lms_dashboard_url,
"is_paid": False,
"support_email": request.site.siteconfiguration.payment_support_email
}

return {}

def get(self, request):
configuration_helpers = request.site.siteconfiguration.edly_client_theme_branding_settings
url = configuration_helpers.get('LUMSXPAY_VOUCHER_API_URL')

if request.user.is_anonymous or not (url and request.basket):
msg = 'user is anonymous cannot proceed to checkout page so redirecting to login'
if not url:
msg = 'Site configuration in ecommerce does not include payment API url'

logger.info(msg)
return redirect_to_login(get_lms_dashboard_url)

if self.is_basket_and_challan_exists(request):
context = self.request_already_existing_challan(request)
if not context:
logger.exception('challan status API not working, no context found')
return HttpResponseNotFound()

return render_to_response('payment/lumsxpay.html', context)

items = self.extract_items_from_basket(request.basket)
payload = {
"name": request.user.username,
"email": request.user.email,
"order_id": request.basket.order_number,
"items": items,
"due_date": self.get_due_date(configuration_helpers)
}

headers = {
"Authorization": configuration_helpers.get('PAYMENT_AUTHORIZATION_KEY'),
"Content-Type": "application/json"
}

try:
response = requests.post(url, data=json.dumps(payload), headers=headers)
except:
logger.exception('Challan generation API not working and cannot be reached.')
return HttpResponseNotFound()

if response.status_code == 200:
voucher_details = response.json()
url_for_online_payment = voucher_details.get('data', {}).get("url_for_online_payment")
url_for_download_voucher = voucher_details.get('data', {}).get("url_for_download_voucher")

context = {
'configuration_helpers': configuration_helpers,
'url_for_online_payment': url_for_online_payment,
'url_for_download_voucher': url_for_download_voucher,
'items_list': items,
"name": request.user.username,
"email": request.user.email,
"order_id": request.basket.order_number,
"user": request.user,
"lms_dashboard_url": get_lms_dashboard_url,
"is_paid": False,
"support_email": request.site.siteconfiguration.payment_support_email
}

voucher_number = voucher_details['data']['voucher_id']
due_date = voucher_details['data']['due_date']

_, created = BasketChallanVoucher.objects.get_or_create(
basket=request.basket,
voucher_number=voucher_number,
due_date=due_date,
is_paid=False
)

if created:
logger.info('challan-basket created with voucher number %s and due date %s', voucher_number, due_date)
else:
logger.exception('could not create the challan voucher entry in DB')
return HttpResponseNotFound()

return render_to_response('payment/lumsxpay.html', context)

logger.exception(
'Challan creation API status return %s status code and challan creation failed with response %s',
response.status_code, response.json()
)

return HttpResponseNotFound()
8 changes: 7 additions & 1 deletion ecommerce/settings/_oscar.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
'ecommerce.extensions.payment.processors.paypal.Paypal',
'ecommerce.extensions.payment.processors.stripe.Stripe',
'ecommerce.extensions.payment.processors.authorizenet.AuthorizeNet',
'ecommerce.extensions.payment.processors.lumsxpay.Lumsxpay',
)

PAYMENT_PROCESSOR_RECEIPT_PATH = '/checkout/receipt/'
Expand Down Expand Up @@ -144,7 +145,12 @@
'merchant_auth_name': None,
'transaction_key': None,
'redirect_url': None
}
},
'lumsxpay': {
'cancel_checkout_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'public_key': None,
'private_key': None,
}
},
}

Expand Down
Loading

0 comments on commit c940633

Please sign in to comment.