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 12, 2021
1 parent f812f58 commit 9aa82a0
Show file tree
Hide file tree
Showing 9 changed files with 413 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2021-01-04 16:01
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)),
('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'))
]

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

from __future__ import unicode_literals

import json
import logging

import requests
from django.db import transaction
from django.http import HttpResponseNotFound
from django.shortcuts import redirect, 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.analytics.utils import translate_basket_line_for_segment
from ecommerce.extensions.basket.models import BasketChallanVoucher
from ecommerce.core.url_utils import get_lms_dashboard_url

logger = logging.getLogger(__name__)

Applicator = get_class('offer.applicator', 'Applicator')
Basket = get_model('basket', 'Basket')
Product = get_model('catalogue', 'Product')
OrderTotalCalculator = get_class('checkout.calculators', 'OrderTotalCalculator')
NoShippingRequired = get_class('shipping.methods', 'NoShippingRequired')


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 voucher_payment(self, request):
configuration_helpers = request.site.siteconfiguration.edly_client_theme_branding_settings
url = configuration_helpers.get('LUMSXPAY_VOUCHER_API_URL')

if not (url and request.basket):
return None

products_basket = [translate_basket_line_for_segment(line) for line in request.basket.all_lines()]

shipping_method = NoShippingRequired()
shipping_charge = shipping_method.calculate(request.basket)
order_total = OrderTotalCalculator().calculate(request.basket, shipping_charge)

user = request.basket.owner

billing_address = None
order = self.handle_order_placement(
order_number=request.basket.order_number,
user=user,
basket=request.basket,
shipping_address=None,
shipping_method=shipping_method,
shipping_charge=shipping_charge,
billing_address=billing_address,
order_total=order_total,
request=None
)
self.handle_post_order(order)

return order

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 is_basket_and_challan_exists(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)

basket = Basket.objects.filter(owner_id=request.user, lines__product_id__in=course_ids).first()
if BasketChallanVoucher.objects.filter(basket=basket).exists():
return True

def create_challan_voucher(self, basket, voucher_number):
try:
return BasketChallanVoucher.objects.create(basket=basket, voucher_number=voucher_number, is_paid=False)
except:
return logger.exception('could not create the challan voucher entry in DB')

def fetch_challan_details(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)

basket = Basket.objects.filter(owner_id=request.user, lines__product_id__in=course_ids).first()
challan_basket = BasketChallanVoucher.objects.filter(basket=basket).first()
voucher_number = challan_basket.voucher_number

configuration_helpers = request.site.siteconfiguration.edly_client_theme_branding_settings
url = configuration_helpers.get('LUMSXPAY_VOUCHER_API_URL') + '/' + voucher_number
headers = {
"Authorization": "Bearer b55a65baa0fb0f586ea9170298c20a3fe3d64460add0bf00e43d191aba9fd0a7",
"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,
}

return HttpResponseNotFound()

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): # change it to login redirect
return HttpResponseNotFound()

try:
if self.is_basket_and_challan_exists(request):
context = self.fetch_challan_details(request)

return render_to_response('payment/lumsxpay.html', context)
else:
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
}

headers = {
"Authorization": "Bearer b55a65baa0fb0f586ea9170298c20a3fe3d64460add0bf00e43d191aba9fd0a7",
"Content-Type": "application/json"}

response = requests.post(url, data=json.dumps(payload), headers=headers)
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
}

voucher_number = voucher_details['data']['voucher_id']
self.create_challan_voucher(request.basket, voucher_number)

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

return HttpResponseNotFound()

except: # pylint: disable=bare-except
logger.exception('could not make the connection with %s for basket %s' % (url, request.basket.id))
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 9aa82a0

Please sign in to comment.