Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
ecommerce
Commits
5f5c0981
Commit
5f5c0981
authored
Oct 24, 2016
by
Marko Jevtić
Committed by
GitHub
Oct 24, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #973 from edx/mjevtic/SOL-1953
[SOL-1953] New Receipt Page Backend
parents
03592581
89ff8dc1
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
375 additions
and
56 deletions
+375
-56
ecommerce/core/migrations/0020_siteconfiguration_enable_otto_receipt_page.py
+19
-0
ecommerce/core/models.py
+6
-0
ecommerce/coupons/tests/mixins.py
+13
-2
ecommerce/extensions/api/serializers.py
+50
-2
ecommerce/extensions/api/v2/tests/views/test_providers.py
+61
-0
ecommerce/extensions/api/v2/urls.py
+7
-1
ecommerce/extensions/api/v2/views/providers.py
+28
-0
ecommerce/extensions/checkout/signals.py
+8
-6
ecommerce/extensions/checkout/tests/test_signals.py
+4
-2
ecommerce/extensions/checkout/utils.py
+43
-0
ecommerce/extensions/fulfillment/modules.py
+12
-5
ecommerce/extensions/fulfillment/tests/test_modules.py
+11
-0
ecommerce/extensions/offer/tests/test_utils.py
+2
-7
ecommerce/extensions/offer/utils.py
+28
-7
ecommerce/extensions/payment/models.py
+1
-0
ecommerce/extensions/payment/processors/cybersource.py
+6
-6
ecommerce/extensions/payment/processors/paypal.py
+1
-5
ecommerce/extensions/payment/tests/processors/test_cybersource.py
+10
-2
ecommerce/extensions/payment/tests/processors/test_paypal.py
+20
-2
ecommerce/extensions/payment/tests/test_views.py
+33
-2
ecommerce/extensions/payment/views.py
+5
-1
ecommerce/extensions/voucher/utils.py
+5
-4
ecommerce/settings/_oscar.py
+1
-1
ecommerce/settings/base.py
+1
-1
No files found.
ecommerce/core/migrations/0020_siteconfiguration_enable_otto_receipt_page.py
0 → 100644
View file @
5f5c0981
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0019_auto_20161012_1404'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'siteconfiguration'
,
name
=
'enable_otto_receipt_page'
,
field
=
models
.
BooleanField
(
default
=
False
,
help_text
=
'Enable the usage of Otto receipt page.'
,
verbose_name
=
'Enable Otto receipt page'
),
),
]
ecommerce/core/models.py
View file @
5f5c0981
...
@@ -108,6 +108,12 @@ class SiteConfiguration(models.Model):
...
@@ -108,6 +108,12 @@ class SiteConfiguration(models.Model):
blank
=
True
,
blank
=
True
,
default
=
""
,
default
=
""
,
)
)
enable_otto_receipt_page
=
models
.
BooleanField
(
verbose_name
=
_
(
'Enable Otto receipt page'
),
help_text
=
_
(
'Enable the usage of Otto receipt page.'
),
blank
=
True
,
default
=
False
)
class
Meta
(
object
):
class
Meta
(
object
):
unique_together
=
(
'site'
,
'partner'
)
unique_together
=
(
'site'
,
'partner'
)
...
...
ecommerce/coupons/tests/mixins.py
View file @
5f5c0981
...
@@ -68,16 +68,27 @@ class CourseCatalogMockMixin(object):
...
@@ -68,16 +68,27 @@ class CourseCatalogMockMixin(object):
}],
}],
}
}
course_run_info_json
=
json
.
dumps
(
course_run_info
)
course_run_info_json
=
json
.
dumps
(
course_run_info
)
course_run_url
=
'{}course_runs/?q={}'
.
format
(
course_run_url
_with_query
=
'{}course_runs/?q={}'
.
format
(
settings
.
COURSE_CATALOG_API_URL
,
settings
.
COURSE_CATALOG_API_URL
,
query
if
query
else
'id:course*'
query
if
query
else
'id:course*'
)
)
httpretty
.
register_uri
(
httpretty
.
register_uri
(
httpretty
.
GET
,
course_run_url
,
httpretty
.
GET
,
course_run_url_with_query
,
body
=
course_run_info_json
,
body
=
course_run_info_json
,
content_type
=
'application/json'
content_type
=
'application/json'
)
)
course_run_url_with_key
=
'{}course_runs/{}/'
.
format
(
settings
.
COURSE_CATALOG_API_URL
,
course_run
.
id
if
course_run
else
'course-v1:test+test+test'
)
httpretty
.
register_uri
(
httpretty
.
GET
,
course_run_url_with_key
,
body
=
json
.
dumps
(
course_run_info
[
'results'
][
0
]),
content_type
=
'application/json'
)
def
mock_dynamic_catalog_contains_api
(
self
,
course_run_ids
,
query
):
def
mock_dynamic_catalog_contains_api
(
self
,
course_run_ids
,
query
):
""" Helper function to register a dynamic course catalog API endpoint for the contains information. """
""" Helper function to register a dynamic course catalog API endpoint for the contains information. """
course_contains_info
=
{
course_contains_info
=
{
...
...
ecommerce/extensions/api/serializers.py
View file @
5f5c0981
...
@@ -208,14 +208,51 @@ class LineSerializer(serializers.ModelSerializer):
...
@@ -208,14 +208,51 @@ class LineSerializer(serializers.ModelSerializer):
class
OrderSerializer
(
serializers
.
ModelSerializer
):
class
OrderSerializer
(
serializers
.
ModelSerializer
):
"""Serializer for parsing order data."""
"""Serializer for parsing order data."""
billing_address
=
BillingAddressSerializer
(
allow_null
=
True
)
date_placed
=
serializers
.
DateTimeField
(
format
=
ISO_8601_FORMAT
)
date_placed
=
serializers
.
DateTimeField
(
format
=
ISO_8601_FORMAT
)
discount
=
serializers
.
SerializerMethodField
()
lines
=
LineSerializer
(
many
=
True
)
lines
=
LineSerializer
(
many
=
True
)
billing_address
=
BillingAddressSerializer
(
allow_null
=
True
)
payment_processor
=
serializers
.
SerializerMethodField
(
)
user
=
UserSerializer
()
user
=
UserSerializer
()
vouchers
=
serializers
.
SerializerMethodField
()
def
get_vouchers
(
self
,
obj
):
try
:
serializer
=
VoucherSerializer
(
obj
.
basket
.
vouchers
.
all
(),
many
=
True
,
context
=
{
'request'
:
self
.
context
[
'request'
]}
)
return
serializer
.
data
except
(
AttributeError
,
ValueError
):
return
None
def
get_payment_processor
(
self
,
obj
):
try
:
return
obj
.
sources
.
all
()[
0
]
.
source_type
.
name
except
IndexError
:
return
None
def
get_discount
(
self
,
obj
):
try
:
discount
=
obj
.
discounts
.
all
()[
0
]
return
str
(
discount
.
amount
)
except
IndexError
:
return
'0'
class
Meta
(
object
):
class
Meta
(
object
):
model
=
Order
model
=
Order
fields
=
(
'number'
,
'date_placed'
,
'status'
,
'currency'
,
'total_excl_tax'
,
'lines'
,
'billing_address'
,
'user'
)
fields
=
(
'billing_address'
,
'currency'
,
'date_placed'
,
'discount'
,
'lines'
,
'number'
,
'payment_processor'
,
'status'
,
'total_excl_tax'
,
'user'
,
'vouchers'
,
)
class
PaymentProcessorSerializer
(
serializers
.
Serializer
):
# pylint: disable=abstract-method
class
PaymentProcessorSerializer
(
serializers
.
Serializer
):
# pylint: disable=abstract-method
...
@@ -640,3 +677,14 @@ class SiteConfigurationSerializer(serializers.ModelSerializer):
...
@@ -640,3 +677,14 @@ class SiteConfigurationSerializer(serializers.ModelSerializer):
class
Meta
(
object
):
class
Meta
(
object
):
model
=
SiteConfiguration
model
=
SiteConfiguration
class
ProviderSerializer
(
serializers
.
Serializer
):
# pylint: disable=abstract-method
description
=
serializers
.
CharField
()
display_name
=
serializers
.
CharField
()
enable_integration
=
serializers
.
BooleanField
()
fulfillment_instructions
=
serializers
.
CharField
()
id
=
serializers
.
CharField
()
status_url
=
serializers
.
CharField
()
thumbnail_url
=
serializers
.
CharField
()
url
=
serializers
.
CharField
()
ecommerce/extensions/api/v2/tests/views/test_providers.py
0 → 100644
View file @
5f5c0981
import
json
import
ddt
from
django.core.urlresolvers
import
reverse
import
httpretty
from
rest_framework
import
status
from
ecommerce.extensions.api.serializers
import
ProviderSerializer
from
ecommerce.tests.testcases
import
TestCase
@ddt.ddt
class
ProvidersViewSetTest
(
TestCase
):
path
=
reverse
(
'api:v2:providers:list_providers'
)
def
setUp
(
self
):
super
(
ProvidersViewSetTest
,
self
)
.
setUp
()
user
=
self
.
create_user
()
self
.
client
.
login
(
username
=
user
.
username
,
password
=
self
.
password
)
self
.
provider
=
'test-provider'
self
.
data
=
{
'id'
:
self
.
provider
,
'display_name'
:
self
.
provider
,
'url'
:
'http://example.com/'
,
'status_url'
:
'http://status.example.com/'
,
'description'
:
'Description'
,
'enable_integration'
:
False
,
'fulfillment_instructions'
:
''
,
'thumbnail_url'
:
'http://thumbnail.example.com/'
,
}
def
mock_provider_api
(
self
):
provider_url
=
'{lms_url}{provider}/'
.
format
(
lms_url
=
self
.
site
.
siteconfiguration
.
build_lms_url
(
'api/credit/v1/providers/'
),
provider
=
self
.
provider
)
httpretty
.
register_uri
(
httpretty
.
GET
,
provider_url
,
body
=
json
.
dumps
(
self
.
data
),
content_type
=
'application/json'
)
@httpretty.activate
def
test_getting_provider
(
self
):
"""Verify endpoint returns correct provider data."""
self
.
mock_provider_api
()
response
=
self
.
client
.
get
(
'{path}?credit_provider_id={provider}'
.
format
(
path
=
self
.
path
,
provider
=
self
.
provider
))
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
ProviderSerializer
(
self
.
data
)
.
data
)
def
test_invalid_provider
(
self
):
"""Verify endpoint response is empty for invalid provider."""
response
=
self
.
client
.
get
(
'{path}?credit_provider_id={provider}'
.
format
(
path
=
self
.
path
,
provider
=
'invalid-provider'
))
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
content
,
''
)
ecommerce/extensions/api/v2/urls.py
View file @
5f5c0981
...
@@ -12,6 +12,7 @@ from ecommerce.extensions.api.v2.views import (
...
@@ -12,6 +12,7 @@ from ecommerce.extensions.api.v2.views import (
partners
as
partner_views
,
partners
as
partner_views
,
payments
as
payment_views
,
payments
as
payment_views
,
products
as
product_views
,
products
as
product_views
,
providers
as
provider_views
,
publication
as
publication_views
,
publication
as
publication_views
,
refunds
as
refund_views
,
refunds
as
refund_views
,
siteconfiguration
as
siteconfiguration_views
,
siteconfiguration
as
siteconfiguration_views
,
...
@@ -65,13 +66,18 @@ ATOMIC_PUBLICATION_URLS = [
...
@@ -65,13 +66,18 @@ ATOMIC_PUBLICATION_URLS = [
),
),
]
]
PROVIDER_URLS
=
[
url
(
r'^$'
,
provider_views
.
ProviderViewSet
.
as_view
(),
name
=
'list_providers'
)
]
urlpatterns
=
[
urlpatterns
=
[
url
(
r'^baskets/'
,
include
(
BASKET_URLS
,
namespace
=
'baskets'
)),
url
(
r'^baskets/'
,
include
(
BASKET_URLS
,
namespace
=
'baskets'
)),
url
(
r'^checkout/$'
,
include
(
CHECKOUT_URLS
,
namespace
=
'checkout'
)),
url
(
r'^checkout/$'
,
include
(
CHECKOUT_URLS
,
namespace
=
'checkout'
)),
url
(
r'^coupons/'
,
include
(
COUPON_URLS
,
namespace
=
'coupons'
)),
url
(
r'^coupons/'
,
include
(
COUPON_URLS
,
namespace
=
'coupons'
)),
url
(
r'^payment/'
,
include
(
PAYMENT_URLS
,
namespace
=
'payment'
)),
url
(
r'^payment/'
,
include
(
PAYMENT_URLS
,
namespace
=
'payment'
)),
url
(
r'^
refunds/'
,
include
(
REFUND_URLS
,
namespace
=
'refund
s'
)),
url
(
r'^
providers/'
,
include
(
PROVIDER_URLS
,
namespace
=
'provider
s'
)),
url
(
r'^publication/'
,
include
(
ATOMIC_PUBLICATION_URLS
,
namespace
=
'publication'
)),
url
(
r'^publication/'
,
include
(
ATOMIC_PUBLICATION_URLS
,
namespace
=
'publication'
)),
url
(
r'^refunds/'
,
include
(
REFUND_URLS
,
namespace
=
'refunds'
)),
]
]
router
=
ExtendedSimpleRouter
()
router
=
ExtendedSimpleRouter
()
...
...
ecommerce/extensions/api/v2/views/providers.py
0 → 100644
View file @
5f5c0981
"""HTTP endpoint for displaying information about providers."""
import
logging
from
rest_framework.views
import
APIView
from
rest_framework.response
import
Response
from
ecommerce.extensions.api.serializers
import
ProviderSerializer
from
ecommerce.extensions.checkout.utils
import
get_credit_provider_details
logger
=
logging
.
getLogger
(
__name__
)
class
ProviderViewSet
(
APIView
):
"""Gets the credit provider data from LMS"""
def
get
(
self
,
request
):
credit_provider_id
=
request
.
GET
.
get
(
'credit_provider_id'
)
provider_data
=
get_credit_provider_details
(
access_token
=
request
.
user
.
access_token
,
credit_provider_id
=
credit_provider_id
,
site_configuration
=
request
.
site
.
siteconfiguration
)
if
not
provider_data
:
response_data
=
None
elif
isinstance
(
provider_data
,
dict
):
response_data
=
ProviderSerializer
(
provider_data
)
.
data
else
:
response_data
=
ProviderSerializer
(
provider_data
,
many
=
True
)
.
data
return
Response
(
response_data
)
ecommerce/extensions/checkout/signals.py
View file @
5f5c0981
import
logging
import
logging
from
django.conf
import
settings
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
oscar.core.loading
import
get_class
from
oscar.core.loading
import
get_class
import
waffle
import
waffle
from
ecommerce.core.url_utils
import
get_lms_url
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
,
silence_exceptions
from
ecommerce.extensions.analytics.utils
import
is_segment_configured
,
parse_tracking_context
,
silence_exceptions
from
ecommerce.extensions.checkout.utils
import
get_credit_provider_details
from
ecommerce.extensions.checkout.utils
import
get_credit_provider_details
,
get_receipt_page_url
from
ecommerce.notifications.notifications
import
send_notification
from
ecommerce.notifications.notifications
import
send_notification
...
@@ -79,15 +77,19 @@ def send_course_purchase_email(sender, order=None, **kwargs): # pylint: disable
...
@@ -79,15 +77,19 @@ def send_course_purchase_email(sender, order=None, **kwargs): # pylint: disable
credit_provider_id
=
credit_provider_id
,
credit_provider_id
=
credit_provider_id
,
site_configuration
=
order
.
site
.
siteconfiguration
site_configuration
=
order
.
site
.
siteconfiguration
)
)
receipt_page_url
=
get_receipt_page_url
(
order_number
=
order
.
number
,
site_configuration
=
order
.
site
.
siteconfiguration
)
if
provider_data
:
if
provider_data
:
send_notification
(
send_notification
(
order
.
user
,
order
.
user
,
'CREDIT_RECEIPT'
,
'CREDIT_RECEIPT'
,
{
{
'course_title'
:
product
.
title
,
'course_title'
:
product
.
title
,
'receipt_page_url'
:
get_lms_url
(
'receipt_page_url'
:
receipt_page_url
,
'{}?orderNum={}'
.
format
(
settings
.
RECEIPT_PAGE_PATH
,
order
.
number
)
),
'credit_hours'
:
product
.
attr
.
credit_hours
,
'credit_hours'
:
product
.
attr
.
credit_hours
,
'credit_provider'
:
provider_data
[
'display_name'
],
'credit_provider'
:
provider_data
[
'display_name'
],
},
},
...
...
ecommerce/extensions/checkout/tests/test_signals.py
View file @
5f5c0981
...
@@ -21,6 +21,8 @@ class SignalTests(CourseCatalogTestMixin, TestCase):
...
@@ -21,6 +21,8 @@ class SignalTests(CourseCatalogTestMixin, TestCase):
def
setUp
(
self
):
def
setUp
(
self
):
super
(
SignalTests
,
self
)
.
setUp
()
super
(
SignalTests
,
self
)
.
setUp
()
self
.
user
=
self
.
create_user
()
self
.
user
=
self
.
create_user
()
self
.
request
.
user
=
self
.
user
self
.
site
.
siteconfiguration
.
enable_otto_receipt_page
=
True
toggle_switch
(
'ENABLE_NOTIFICATIONS'
,
True
)
toggle_switch
(
'ENABLE_NOTIFICATIONS'
,
True
)
def
prepare_order
(
self
,
seat_type
,
credit_provider_id
=
None
):
def
prepare_order
(
self
,
seat_type
,
credit_provider_id
=
None
):
...
@@ -87,8 +89,8 @@ class SignalTests(CourseCatalogTestMixin, TestCase):
...
@@ -87,8 +89,8 @@ class SignalTests(CourseCatalogTestMixin, TestCase):
credit_hours
=
2
,
credit_hours
=
2
,
credit_provider_name
=
credit_provider_name
,
credit_provider_name
=
credit_provider_name
,
platform_name
=
self
.
site
.
name
,
platform_name
=
self
.
site
.
name
,
receipt_url
=
self
.
site
.
siteconfiguration
.
build_
lms
_url
(
receipt_url
=
self
.
site
.
siteconfiguration
.
build_
ecommerce
_url
(
'{}
?orderNum=
{}'
.
format
(
settings
.
RECEIPT_PAGE_PATH
,
order
.
number
)
'{}{}'
.
format
(
settings
.
RECEIPT_PAGE_PATH
,
order
.
number
)
)
)
)
)
)
)
...
...
ecommerce/extensions/checkout/utils.py
View file @
5f5c0981
import
logging
import
logging
from
babel.numbers
import
format_currency
from
django.conf
import
settings
from
django.utils.translation
import
get_language
,
to_locale
from
edx_rest_api_client.client
import
EdxRestApiClient
from
edx_rest_api_client.client
import
EdxRestApiClient
from
requests.exceptions
import
ConnectionError
,
Timeout
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
SlumberHttpBaseException
from
slumber.exceptions
import
SlumberHttpBaseException
...
@@ -26,3 +29,43 @@ def get_credit_provider_details(access_token, credit_provider_id, site_configura
...
@@ -26,3 +29,43 @@ def get_credit_provider_details(access_token, credit_provider_id, site_configura
except
(
ConnectionError
,
SlumberHttpBaseException
,
Timeout
):
except
(
ConnectionError
,
SlumberHttpBaseException
,
Timeout
):
logger
.
exception
(
'Failed to retrieve credit provider details for provider [
%
s].'
,
credit_provider_id
)
logger
.
exception
(
'Failed to retrieve credit provider details for provider [
%
s].'
,
credit_provider_id
)
return
None
return
None
def
get_receipt_page_url
(
site_configuration
,
order_number
=
None
):
""" Returns the receipt page URL.
Args:
order_number (str): Order number
site_configuration (SiteConfiguration): Site Configuration containing the flag for enabling Otto receipt page.
Returns:
str: Receipt page URL.
"""
if
site_configuration
.
enable_otto_receipt_page
:
return
site_configuration
.
build_ecommerce_url
(
'{base_url}{order_number}'
.
format
(
base_url
=
settings
.
RECEIPT_PAGE_PATH
,
order_number
=
order_number
if
order_number
else
''
))
return
site_configuration
.
build_lms_url
(
'{base_url}{order_number}'
.
format
(
base_url
=
'/commerce/checkout/receipt'
,
order_number
=
'?orderNum={}'
.
format
(
order_number
)
if
order_number
else
''
)
)
def
add_currency
(
amount
):
""" Adds currency to the price amount.
Args:
amount (Decimal): Price amount
Returns:
str: Formatted price with currency.
"""
return
format_currency
(
amount
,
settings
.
OSCAR_DEFAULT_CURRENCY
,
format
=
u'#,##0.00'
,
locale
=
to_locale
(
get_language
())
)
ecommerce/extensions/fulfillment/modules.py
View file @
5f5c0981
...
@@ -16,10 +16,11 @@ import requests
...
@@ -16,10 +16,11 @@ import requests
from
requests.exceptions
import
ConnectionError
,
Timeout
from
requests.exceptions
import
ConnectionError
,
Timeout
from
ecommerce.core.constants
import
ENROLLMENT_CODE_PRODUCT_CLASS_NAME
from
ecommerce.core.constants
import
ENROLLMENT_CODE_PRODUCT_CLASS_NAME
from
ecommerce.core.url_utils
import
get_
ecommerce_url
,
get_lms_enrollment_api_url
,
get_lms
_url
from
ecommerce.core.url_utils
import
get_
lms_enrollment_api
_url
from
ecommerce.courses.models
import
Course
from
ecommerce.courses.models
import
Course
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.extensions.analytics.utils
import
audit_log
,
parse_tracking_context
from
ecommerce.extensions.analytics.utils
import
audit_log
,
parse_tracking_context
from
ecommerce.extensions.checkout.utils
import
get_receipt_page_url
from
ecommerce.extensions.fulfillment.status
import
LINE
from
ecommerce.extensions.fulfillment.status
import
LINE
from
ecommerce.extensions.voucher.models
import
OrderLineVouchers
from
ecommerce.extensions.voucher.models
import
OrderLineVouchers
from
ecommerce.extensions.voucher.utils
import
create_vouchers
from
ecommerce.extensions.voucher.utils
import
create_vouchers
...
@@ -446,18 +447,24 @@ class EnrollmentCodeFulfillmentModule(BaseFulfillmentModule):
...
@@ -446,18 +447,24 @@ class EnrollmentCodeFulfillmentModule(BaseFulfillmentModule):
# Note (multi-courses): Change from a course_name to a list of course names.
# Note (multi-courses): Change from a course_name to a list of course names.
product
=
order
.
lines
.
first
()
.
product
product
=
order
.
lines
.
first
()
.
product
course
=
Course
.
objects
.
get
(
id
=
product
.
attr
.
course_key
)
course
=
Course
.
objects
.
get
(
id
=
product
.
attr
.
course_key
)
receipt_page_url
=
get_receipt_page_url
(
order_number
=
order
.
number
,
site_configuration
=
order
.
site
.
siteconfiguration
)
send_notification
(
send_notification
(
order
.
user
,
order
.
user
,
'ORDER_WITH_CSV'
,
'ORDER_WITH_CSV'
,
context
=
{
context
=
{
'contact_url'
:
get
_lms_url
(
'/contact'
),
'contact_url'
:
order
.
site
.
siteconfiguration
.
build
_lms_url
(
'/contact'
),
'course_name'
:
course
.
name
,
'course_name'
:
course
.
name
,
'download_csv_link'
:
get_ecommerce_url
(
reverse
(
'coupons:enrollment_code_csv'
,
args
=
[
order
.
number
])),
'download_csv_link'
:
order
.
site
.
siteconfiguration
.
build_ecommerce_url
(
reverse
(
'coupons:enrollment_code_csv'
,
args
=
[
order
.
number
])
),
'enrollment_code_title'
:
product
.
title
,
'enrollment_code_title'
:
product
.
title
,
'lms_url'
:
order
.
site
.
siteconfiguration
.
build_lms_url
(),
'order_number'
:
order
.
number
,
'order_number'
:
order
.
number
,
'partner_name'
:
order
.
site
.
siteconfiguration
.
partner
.
name
,
'partner_name'
:
order
.
site
.
siteconfiguration
.
partner
.
name
,
'lms_url'
:
get_lms_url
(),
'receipt_page_url'
:
receipt_page_url
,
'receipt_page_url'
:
get_lms_url
(
'{}?orderNum={}'
.
format
(
settings
.
RECEIPT_PAGE_PATH
,
order
.
number
)),
},
},
site
=
order
.
site
site
=
order
.
site
)
)
ecommerce/extensions/fulfillment/tests/test_modules.py
View file @
5f5c0981
...
@@ -443,6 +443,7 @@ class EnrollmentCodeFulfillmentModuleTests(CourseCatalogTestMixin, TestCase):
...
@@ -443,6 +443,7 @@ class EnrollmentCodeFulfillmentModuleTests(CourseCatalogTestMixin, TestCase):
def
setUp
(
self
):
def
setUp
(
self
):
super
(
EnrollmentCodeFulfillmentModuleTests
,
self
)
.
setUp
()
super
(
EnrollmentCodeFulfillmentModuleTests
,
self
)
.
setUp
()
toggle_switch
(
ENROLLMENT_CODE_SWITCH
,
True
)
toggle_switch
(
ENROLLMENT_CODE_SWITCH
,
True
)
self
.
site
.
siteconfiguration
.
enable_otto_receipt_page
=
True
course
=
CourseFactory
()
course
=
CourseFactory
()
course
.
create_or_update_seat
(
'verified'
,
True
,
50
,
self
.
partner
,
create_enrollment_code
=
True
)
course
.
create_or_update_seat
(
'verified'
,
True
,
50
,
self
.
partner
,
create_enrollment_code
=
True
)
enrollment_code
=
Product
.
objects
.
get
(
product_class__name
=
ENROLLMENT_CODE_PRODUCT_CLASS_NAME
)
enrollment_code
=
Product
.
objects
.
get
(
product_class__name
=
ENROLLMENT_CODE_PRODUCT_CLASS_NAME
)
...
@@ -477,6 +478,16 @@ class EnrollmentCodeFulfillmentModuleTests(CourseCatalogTestMixin, TestCase):
...
@@ -477,6 +478,16 @@ class EnrollmentCodeFulfillmentModuleTests(CourseCatalogTestMixin, TestCase):
self
.
assertEqual
(
OrderLineVouchers
.
objects
.
count
(),
1
)
self
.
assertEqual
(
OrderLineVouchers
.
objects
.
count
(),
1
)
self
.
assertEqual
(
OrderLineVouchers
.
objects
.
first
()
.
vouchers
.
count
(),
self
.
QUANTITY
)
self
.
assertEqual
(
OrderLineVouchers
.
objects
.
first
()
.
vouchers
.
count
(),
self
.
QUANTITY
)
def
test_fulfill_product_with_lms_receipt_page
(
self
):
"""Test disabling otto_receipt_page switch still results in successfully fulfilling Enrollment code product."""
self
.
site
.
siteconfiguration
.
enable_otto_receipt_page
=
False
self
.
assertEqual
(
OrderLineVouchers
.
objects
.
count
(),
0
)
lines
=
self
.
order
.
lines
.
all
()
__
,
completed_lines
=
EnrollmentCodeFulfillmentModule
()
.
fulfill_product
(
self
.
order
,
lines
)
self
.
assertEqual
(
completed_lines
[
0
]
.
status
,
LINE
.
COMPLETE
)
self
.
assertEqual
(
OrderLineVouchers
.
objects
.
count
(),
1
)
self
.
assertEqual
(
OrderLineVouchers
.
objects
.
first
()
.
vouchers
.
count
(),
self
.
QUANTITY
)
def
test_revoke_line
(
self
):
def
test_revoke_line
(
self
):
line
=
self
.
order
.
lines
.
first
()
line
=
self
.
order
.
lines
.
first
()
with
self
.
assertRaises
(
NotImplementedError
):
with
self
.
assertRaises
(
NotImplementedError
):
...
...
ecommerce/extensions/offer/tests/test_utils.py
View file @
5f5c0981
from
decimal
import
Decimal
from
decimal
import
Decimal
import
ddt
import
ddt
from
babel.numbers
import
format_currency
from
django.conf
import
settings
from
django.utils.translation
import
get_language
,
to_locale
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
oscar.test.factories
import
*
# pylint:disable=wildcard-import,unused-wildcard-import
from
oscar.test.factories
import
*
# pylint:disable=wildcard-import,unused-wildcard-import
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.extensions.checkout.utils
import
add_currency
from
ecommerce.extensions.offer.utils
import
_remove_exponent_and_trailing_zeros
,
format_benefit_value
from
ecommerce.extensions.offer.utils
import
_remove_exponent_and_trailing_zeros
,
format_benefit_value
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
...
@@ -36,9 +33,7 @@ class UtilTests(CourseCatalogTestMixin, TestCase):
...
@@ -36,9 +33,7 @@ class UtilTests(CourseCatalogTestMixin, TestCase):
self
.
assertEqual
(
benefit_value
,
'35
%
'
)
self
.
assertEqual
(
benefit_value
,
'35
%
'
)
benefit_value
=
format_benefit_value
(
self
.
value_benefit
)
benefit_value
=
format_benefit_value
(
self
.
value_benefit
)
expected_benefit
=
format_currency
(
expected_benefit
=
add_currency
(
Decimal
((
self
.
seat_price
-
10
)))
Decimal
((
self
.
seat_price
-
10
)),
settings
.
OSCAR_DEFAULT_CURRENCY
,
format
=
u'#,##0.00'
,
locale
=
to_locale
(
get_language
()))
self
.
assertEqual
(
benefit_value
,
'${expected_benefit}'
.
format
(
expected_benefit
=
expected_benefit
))
self
.
assertEqual
(
benefit_value
,
'${expected_benefit}'
.
format
(
expected_benefit
=
expected_benefit
))
@ddt.data
(
@ddt.data
(
...
...
ecommerce/extensions/offer/utils.py
View file @
5f5c0981
"""Offer Utility Methods. """
"""Offer Utility Methods. """
from
decimal
import
Decimal
from
decimal
import
Decimal
from
babel.numbers
import
format_currency
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.conf
import
settings
from
django.utils.translation
import
get_language
,
to_locale
,
ugettext_lazy
as
_
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
ecommerce.extensions.checkout.utils
import
add_currency
Benefit
=
get_model
(
'offer'
,
'Benefit'
)
Benefit
=
get_model
(
'offer'
,
'Benefit'
)
...
@@ -23,6 +22,30 @@ def _remove_exponent_and_trailing_zeros(decimal):
...
@@ -23,6 +22,30 @@ def _remove_exponent_and_trailing_zeros(decimal):
return
decimal
.
quantize
(
Decimal
(
1
))
if
decimal
==
decimal
.
to_integral
()
else
decimal
.
normalize
()
return
decimal
.
quantize
(
Decimal
(
1
))
if
decimal
==
decimal
.
to_integral
()
else
decimal
.
normalize
()
def
get_discount_percentage
(
discount_value
,
product_price
):
"""
Get discount percentage of discount value applied to a product price.
Arguments:
discount_value (float): Discount value
product_price (float): Price of a product the discount is used on
Returns:
float: Discount percentage
"""
return
discount_value
/
product_price
*
100
def
get_discount_value
(
discount_percentage
,
product_price
):
"""
Get discount value of discount percentage applied to a product price.
Arguments:
discount_percentage (float): Discount percentage
product_price (float): Price of a product the discount is used on
Returns:
float: Discount value
"""
return
discount_percentage
*
product_price
/
100.0
def
format_benefit_value
(
benefit
):
def
format_benefit_value
(
benefit
):
"""
"""
Format benefit value for display based on the benefit type
Format benefit value for display based on the benefit type
...
@@ -37,8 +60,6 @@ def format_benefit_value(benefit):
...
@@ -37,8 +60,6 @@ def format_benefit_value(benefit):
if
benefit
.
type
==
Benefit
.
PERCENTAGE
:
if
benefit
.
type
==
Benefit
.
PERCENTAGE
:
benefit_value
=
_
(
'{benefit_value}
%
'
.
format
(
benefit_value
=
benefit_value
))
benefit_value
=
_
(
'{benefit_value}
%
'
.
format
(
benefit_value
=
benefit_value
))
else
:
else
:
converted_benefit
=
format_currency
(
converted_benefit
=
add_currency
(
Decimal
(
benefit
.
value
))
Decimal
(
benefit
.
value
),
settings
.
OSCAR_DEFAULT_CURRENCY
,
format
=
u'#,##0.00'
,
locale
=
to_locale
(
get_language
()))
benefit_value
=
_
(
'${benefit_value}'
.
format
(
benefit_value
=
converted_benefit
))
benefit_value
=
_
(
'${benefit_value}'
.
format
(
benefit_value
=
converted_benefit
))
return
benefit_value
return
benefit_value
ecommerce/extensions/payment/models.py
View file @
5f5c0981
...
@@ -17,6 +17,7 @@ class PaymentProcessorResponse(models.Model):
...
@@ -17,6 +17,7 @@ class PaymentProcessorResponse(models.Model):
created
=
models
.
DateTimeField
(
auto_now_add
=
True
,
db_index
=
True
)
created
=
models
.
DateTimeField
(
auto_now_add
=
True
,
db_index
=
True
)
class
Meta
(
object
):
class
Meta
(
object
):
get_latest_by
=
'created'
index_together
=
(
'processor_name'
,
'transaction_id'
)
index_together
=
(
'processor_name'
,
'transaction_id'
)
verbose_name
=
_
(
'Payment Processor Response'
)
verbose_name
=
_
(
'Payment Processor Response'
)
verbose_name_plural
=
_
(
'Payment Processor Responses'
)
verbose_name_plural
=
_
(
'Payment Processor Responses'
)
...
...
ecommerce/extensions/payment/processors/cybersource.py
View file @
5f5c0981
...
@@ -14,8 +14,9 @@ from suds.sudsobject import asdict
...
@@ -14,8 +14,9 @@ from suds.sudsobject import asdict
from
suds.wsse
import
Security
,
UsernameToken
from
suds.wsse
import
Security
,
UsernameToken
from
threadlocals.threadlocals
import
get_current_request
from
threadlocals.threadlocals
import
get_current_request
from
ecommerce.core.url_utils
import
get_ecommerce_url
,
get_lms_url
from
ecommerce.core.constants
import
ISO_8601_FORMAT
from
ecommerce.core.constants
import
ISO_8601_FORMAT
from
ecommerce.core.url_utils
import
get_ecommerce_url
from
ecommerce.extensions.checkout.utils
import
get_receipt_page_url
from
ecommerce.extensions.order.constants
import
PaymentEventTypeName
from
ecommerce.extensions.order.constants
import
PaymentEventTypeName
from
ecommerce.extensions.payment.constants
import
CYBERSOURCE_CARD_TYPE_MAP
from
ecommerce.extensions.payment.constants
import
CYBERSOURCE_CARD_TYPE_MAP
from
ecommerce.extensions.payment.exceptions
import
(
InvalidSignatureError
,
InvalidCybersourceDecision
,
from
ecommerce.extensions.payment.exceptions
import
(
InvalidSignatureError
,
InvalidCybersourceDecision
,
...
@@ -64,10 +65,6 @@ class Cybersource(BasePaymentProcessor):
...
@@ -64,10 +65,6 @@ class Cybersource(BasePaymentProcessor):
self
.
language_code
=
settings
.
LANGUAGE_CODE
self
.
language_code
=
settings
.
LANGUAGE_CODE
@property
@property
def
receipt_page_url
(
self
):
return
get_lms_url
(
self
.
configuration
[
'receipt_path'
])
@property
def
cancel_page_url
(
self
):
def
cancel_page_url
(
self
):
return
get_ecommerce_url
(
self
.
configuration
[
'cancel_checkout_path'
])
return
get_ecommerce_url
(
self
.
configuration
[
'cancel_checkout_path'
])
...
@@ -98,7 +95,10 @@ class Cybersource(BasePaymentProcessor):
...
@@ -98,7 +95,10 @@ class Cybersource(BasePaymentProcessor):
'amount'
:
str
(
basket
.
total_incl_tax
),
'amount'
:
str
(
basket
.
total_incl_tax
),
'currency'
:
basket
.
currency
,
'currency'
:
basket
.
currency
,
'consumer_id'
:
basket
.
owner
.
username
,
'consumer_id'
:
basket
.
owner
.
username
,
'override_custom_receipt_page'
:
'{}?orderNum={}'
.
format
(
self
.
receipt_page_url
,
basket
.
order_number
),
'override_custom_receipt_page'
:
get_receipt_page_url
(
order_number
=
basket
.
order_number
,
site_configuration
=
basket
.
site
.
siteconfiguration
),
'override_custom_cancel_page'
:
self
.
cancel_page_url
,
'override_custom_cancel_page'
:
self
.
cancel_page_url
,
}
}
...
...
ecommerce/extensions/payment/processors/paypal.py
View file @
5f5c0981
...
@@ -10,7 +10,7 @@ from oscar.core.loading import get_model
...
@@ -10,7 +10,7 @@ from oscar.core.loading import get_model
import
paypalrestsdk
import
paypalrestsdk
import
waffle
import
waffle
from
ecommerce.core.url_utils
import
get_ecommerce_url
,
get_lms_url
from
ecommerce.core.url_utils
import
get_ecommerce_url
from
ecommerce.extensions.order.constants
import
PaymentEventTypeName
from
ecommerce.extensions.order.constants
import
PaymentEventTypeName
from
ecommerce.extensions.payment.processors
import
BasePaymentProcessor
from
ecommerce.extensions.payment.processors
import
BasePaymentProcessor
from
ecommerce.extensions.payment.models
import
PaypalWebProfile
from
ecommerce.extensions.payment.models
import
PaypalWebProfile
...
@@ -60,10 +60,6 @@ class Paypal(BasePaymentProcessor):
...
@@ -60,10 +60,6 @@ class Paypal(BasePaymentProcessor):
})
})
@property
@property
def
receipt_url
(
self
):
return
get_lms_url
(
self
.
configuration
[
'receipt_path'
])
@property
def
cancel_url
(
self
):
def
cancel_url
(
self
):
return
get_ecommerce_url
(
self
.
configuration
[
'cancel_checkout_path'
])
return
get_ecommerce_url
(
self
.
configuration
[
'cancel_checkout_path'
])
...
...
ecommerce/extensions/payment/tests/processors/test_cybersource.py
View file @
5f5c0981
...
@@ -17,6 +17,7 @@ from oscar.test import factories
...
@@ -17,6 +17,7 @@ from oscar.test import factories
from
threadlocals.threadlocals
import
get_current_request
from
threadlocals.threadlocals
import
get_current_request
from
ecommerce.core.constants
import
ISO_8601_FORMAT
from
ecommerce.core.constants
import
ISO_8601_FORMAT
from
ecommerce.extensions.checkout.utils
import
get_receipt_page_url
from
ecommerce.extensions.payment.exceptions
import
(
from
ecommerce.extensions.payment.exceptions
import
(
InvalidSignatureError
,
InvalidCybersourceDecision
,
PartialAuthorizationError
InvalidSignatureError
,
InvalidCybersourceDecision
,
PartialAuthorizationError
)
)
...
@@ -38,6 +39,11 @@ class CybersourceTests(CybersourceMixin, PaymentProcessorTestCaseMixin, TestCase
...
@@ -38,6 +39,11 @@ class CybersourceTests(CybersourceMixin, PaymentProcessorTestCaseMixin, TestCase
processor_class
=
Cybersource
processor_class
=
Cybersource
processor_name
=
'cybersource'
processor_name
=
'cybersource'
def
setUp
(
self
):
super
(
CybersourceTests
,
self
)
.
setUp
()
self
.
site
.
siteconfiguration
.
enable_otto_receipt_page
=
True
self
.
basket
.
site
=
self
.
site
def
get_expected_transaction_parameters
(
self
,
transaction_uuid
,
include_level_2_3_details
=
True
):
def
get_expected_transaction_parameters
(
self
,
transaction_uuid
,
include_level_2_3_details
=
True
):
"""
"""
Builds expected transaction parameters dictionary
Builds expected transaction parameters dictionary
...
@@ -58,8 +64,10 @@ class CybersourceTests(CybersourceMixin, PaymentProcessorTestCaseMixin, TestCase
...
@@ -58,8 +64,10 @@ class CybersourceTests(CybersourceMixin, PaymentProcessorTestCaseMixin, TestCase
'amount'
:
unicode
(
self
.
basket
.
total_incl_tax
),
'amount'
:
unicode
(
self
.
basket
.
total_incl_tax
),
'currency'
:
self
.
basket
.
currency
,
'currency'
:
self
.
basket
.
currency
,
'consumer_id'
:
self
.
basket
.
owner
.
username
,
'consumer_id'
:
self
.
basket
.
owner
.
username
,
'override_custom_receipt_page'
:
'{}?orderNum={}'
.
format
(
self
.
processor
.
receipt_page_url
,
'override_custom_receipt_page'
:
get_receipt_page_url
(
self
.
basket
.
order_number
),
order_number
=
self
.
basket
.
order_number
,
site_configuration
=
self
.
basket
.
site
.
siteconfiguration
),
'override_custom_cancel_page'
:
self
.
processor
.
cancel_page_url
,
'override_custom_cancel_page'
:
self
.
processor
.
cancel_page_url
,
'merchant_defined_data1'
:
self
.
course
.
id
,
'merchant_defined_data1'
:
self
.
course
.
id
,
'merchant_defined_data2'
:
self
.
CERTIFICATE_TYPE
,
'merchant_defined_data2'
:
self
.
CERTIFICATE_TYPE
,
...
...
ecommerce/extensions/payment/tests/processors/test_paypal.py
View file @
5f5c0981
...
@@ -20,7 +20,7 @@ from paypalrestsdk.resource import Resource
...
@@ -20,7 +20,7 @@ from paypalrestsdk.resource import Resource
from
testfixtures
import
LogCapture
from
testfixtures
import
LogCapture
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.
core.url_utils
import
get_ecommerc
e_url
from
ecommerce.
extensions.checkout.utils
import
get_receipt_pag
e_url
from
ecommerce.extensions.payment.models
import
PaypalWebProfile
from
ecommerce.extensions.payment.models
import
PaypalWebProfile
from
ecommerce.extensions.payment.processors.paypal
import
Paypal
from
ecommerce.extensions.payment.processors.paypal
import
Paypal
from
ecommerce.extensions.payment.tests.mixins
import
PaypalMixin
from
ecommerce.extensions.payment.tests.mixins
import
PaypalMixin
...
@@ -82,6 +82,10 @@ class PaypalTests(PaypalMixin, PaymentProcessorTestCaseMixin, TestCase):
...
@@ -82,6 +82,10 @@ class PaypalTests(PaypalMixin, PaymentProcessorTestCaseMixin, TestCase):
actual
=
self
.
processor
.
get_transaction_parameters
(
self
.
basket
,
request
=
self
.
request
)
actual
=
self
.
processor
.
get_transaction_parameters
(
self
.
basket
,
request
=
self
.
request
)
self
.
assertEqual
(
actual
,
expected
)
self
.
assertEqual
(
actual
,
expected
)
def
_get_receipt_url
(
self
):
"""DRY helper for getting receipt page URL."""
return
get_receipt_page_url
(
site_configuration
=
self
.
site
.
siteconfiguration
)
def
_assert_payment_event_and_source
(
self
,
payer_info
):
def
_assert_payment_event_and_source
(
self
,
payer_info
):
"""DRY helper for verifying a payment event and source."""
"""DRY helper for verifying a payment event and source."""
source
,
payment_event
=
self
.
processor
.
handle_processor_response
(
self
.
RETURN_DATA
,
basket
=
self
.
basket
)
source
,
payment_event
=
self
.
processor
.
handle_processor_response
(
self
.
RETURN_DATA
,
basket
=
self
.
basket
)
...
@@ -107,9 +111,23 @@ class PaypalTests(PaypalMixin, PaymentProcessorTestCaseMixin, TestCase):
...
@@ -107,9 +111,23 @@ class PaypalTests(PaypalMixin, PaymentProcessorTestCaseMixin, TestCase):
self
.
assert_processor_response_recorded
(
self
.
processor
.
NAME
,
self
.
PAYMENT_ID
,
response
,
basket
=
self
.
basket
)
self
.
assert_processor_response_recorded
(
self
.
processor
.
NAME
,
self
.
PAYMENT_ID
,
response
,
basket
=
self
.
basket
)
last_request_body
=
json
.
loads
(
httpretty
.
last_request
()
.
body
)
last_request_body
=
json
.
loads
(
httpretty
.
last_request
()
.
body
)
expected
=
urljoin
(
get
_ecommerce_url
(),
reverse
(
'paypal_execute'
))
expected
=
urljoin
(
self
.
site
.
siteconfiguration
.
build
_ecommerce_url
(),
reverse
(
'paypal_execute'
))
self
.
assertEqual
(
last_request_body
[
'redirect_urls'
][
'return_url'
],
expected
)
self
.
assertEqual
(
last_request_body
[
'redirect_urls'
][
'return_url'
],
expected
)
def
test_switch_enabled_otto_url
(
self
):
"""
Ensures that when the otto_receipt_page waffle switch is enabled, the processor uses the new receipt page.
"""
self
.
site
.
siteconfiguration
.
enable_otto_receipt_page
=
True
assert
self
.
_get_receipt_url
()
==
self
.
site
.
siteconfiguration
.
build_ecommerce_url
(
settings
.
RECEIPT_PAGE_PATH
)
def
test_switch_disabled_lms_url
(
self
):
"""
Ensures that when the otto_receipt_page waffle switch is disabled, the processor uses the LMS receipt page.
"""
self
.
site
.
siteconfiguration
.
enable_otto_receipt_page
=
False
assert
self
.
_get_receipt_url
()
==
self
.
site
.
siteconfiguration
.
build_lms_url
(
'/commerce/checkout/receipt'
)
@httpretty.activate
@httpretty.activate
@mock.patch
(
'ecommerce.extensions.payment.processors.paypal.paypalrestsdk.Payment'
)
@mock.patch
(
'ecommerce.extensions.payment.processors.paypal.paypalrestsdk.Payment'
)
@ddt.data
(
None
,
Paypal
.
DEFAULT_PROFILE_NAME
,
"some-other-name"
)
@ddt.data
(
None
,
Paypal
.
DEFAULT_PROFILE_NAME
,
"some-other-name"
)
...
...
ecommerce/extensions/payment/tests/test_views.py
View file @
5f5c0981
...
@@ -11,6 +11,7 @@ from oscar.test import factories
...
@@ -11,6 +11,7 @@ from oscar.test import factories
from
oscar.test.contextmanagers
import
mock_signal_receiver
from
oscar.test.contextmanagers
import
mock_signal_receiver
from
testfixtures
import
LogCapture
from
testfixtures
import
LogCapture
from
ecommerce.extensions.checkout.utils
import
get_receipt_page_url
from
ecommerce.extensions.fulfillment.status
import
ORDER
from
ecommerce.extensions.fulfillment.status
import
ORDER
from
ecommerce.extensions.payment.processors.cybersource
import
Cybersource
from
ecommerce.extensions.payment.processors.cybersource
import
Cybersource
from
ecommerce.extensions.payment.processors.paypal
import
Paypal
from
ecommerce.extensions.payment.processors.paypal
import
Paypal
...
@@ -36,6 +37,8 @@ class CybersourceNotifyViewTests(CybersourceMixin, PaymentEventsMixin, TestCase)
...
@@ -36,6 +37,8 @@ class CybersourceNotifyViewTests(CybersourceMixin, PaymentEventsMixin, TestCase)
def
setUp
(
self
):
def
setUp
(
self
):
super
(
CybersourceNotifyViewTests
,
self
)
.
setUp
()
super
(
CybersourceNotifyViewTests
,
self
)
.
setUp
()
self
.
site
.
siteconfiguration
.
enable_otto_receipt_page
=
True
self
.
user
=
factories
.
UserFactory
()
self
.
user
=
factories
.
UserFactory
()
self
.
billing_address
=
self
.
make_billing_address
()
self
.
billing_address
=
self
.
make_billing_address
()
...
@@ -301,6 +304,7 @@ class PaypalPaymentExecutionViewTests(PaypalMixin, PaymentEventsMixin, TestCase)
...
@@ -301,6 +304,7 @@ class PaypalPaymentExecutionViewTests(PaypalMixin, PaymentEventsMixin, TestCase)
self
.
basket
=
factories
.
create_basket
()
self
.
basket
=
factories
.
create_basket
()
self
.
basket
.
owner
=
factories
.
UserFactory
()
self
.
basket
.
owner
=
factories
.
UserFactory
()
self
.
basket
.
site
=
self
.
site
self
.
basket
.
freeze
()
self
.
basket
.
freeze
()
self
.
processor
=
Paypal
()
self
.
processor
=
Paypal
()
...
@@ -312,7 +316,7 @@ class PaypalPaymentExecutionViewTests(PaypalMixin, PaymentEventsMixin, TestCase)
...
@@ -312,7 +316,7 @@ class PaypalPaymentExecutionViewTests(PaypalMixin, PaymentEventsMixin, TestCase)
@httpretty.activate
@httpretty.activate
def
_assert_execution_redirect
(
self
,
payer_info
=
None
,
url_redirect
=
None
):
def
_assert_execution_redirect
(
self
,
payer_info
=
None
,
url_redirect
=
None
):
"""Verify redirection to
the configured
receipt page after attempted payment execution."""
"""Verify redirection to
Otto
receipt page after attempted payment execution."""
self
.
mock_oauth2_response
()
self
.
mock_oauth2_response
()
# Create a payment record the view can use to retrieve a basket
# Create a payment record the view can use to retrieve a basket
...
@@ -325,7 +329,10 @@ class PaypalPaymentExecutionViewTests(PaypalMixin, PaymentEventsMixin, TestCase)
...
@@ -325,7 +329,10 @@ class PaypalPaymentExecutionViewTests(PaypalMixin, PaymentEventsMixin, TestCase)
response
=
self
.
client
.
get
(
reverse
(
'paypal_execute'
),
self
.
RETURN_DATA
)
response
=
self
.
client
.
get
(
reverse
(
'paypal_execute'
),
self
.
RETURN_DATA
)
self
.
assertRedirects
(
self
.
assertRedirects
(
response
,
response
,
url_redirect
or
u'{}?orderNum={}'
.
format
(
self
.
processor
.
receipt_url
,
self
.
basket
.
order_number
),
url_redirect
or
get_receipt_page_url
(
order_number
=
self
.
basket
.
order_number
,
site_configuration
=
self
.
basket
.
site
.
siteconfiguration
),
fetch_redirect_response
=
False
fetch_redirect_response
=
False
)
)
...
@@ -357,6 +364,30 @@ class PaypalPaymentExecutionViewTests(PaypalMixin, PaymentEventsMixin, TestCase)
...
@@ -357,6 +364,30 @@ class PaypalPaymentExecutionViewTests(PaypalMixin, PaymentEventsMixin, TestCase)
(
logger_name
,
'ERROR'
,
error_message
)
(
logger_name
,
'ERROR'
,
error_message
)
)
)
@httpretty.activate
def
test_execution_redirect_to_lms
(
self
):
"""
Verify redirection to LMS receipt page after attempted payment execution if Otto receipt page waffle
switch is disabled.
"""
self
.
site
.
siteconfiguration
.
enable_otto_receipt_page
=
False
self
.
mock_oauth2_response
()
# Create a payment record the view can use to retrieve a basket
self
.
mock_payment_creation_response
(
self
.
basket
)
self
.
processor
.
get_transaction_parameters
(
self
.
basket
,
request
=
self
.
request
)
self
.
mock_payment_execution_response
(
self
.
basket
)
response
=
self
.
client
.
get
(
reverse
(
'paypal_execute'
),
self
.
RETURN_DATA
)
self
.
assertRedirects
(
response
,
get_receipt_page_url
(
order_number
=
self
.
basket
.
order_number
,
site_configuration
=
self
.
basket
.
site
.
siteconfiguration
),
fetch_redirect_response
=
False
)
@ddt.data
(
@ddt.data
(
None
,
# falls back to PaypalMixin.PAYER_INFO, a fully-populated payer_info object
None
,
# falls back to PaypalMixin.PAYER_INFO, a fully-populated payer_info object
{
"shipping_address"
:
None
},
# minimal data, which may be sent in some Paypal execution responses
{
"shipping_address"
:
None
},
# minimal data, which may be sent in some Paypal execution responses
...
...
ecommerce/extensions/payment/views.py
View file @
5f5c0981
...
@@ -16,6 +16,7 @@ from oscar.apps.payment.exceptions import PaymentError, UserCancelled, Transacti
...
@@ -16,6 +16,7 @@ from oscar.apps.payment.exceptions import PaymentError, UserCancelled, Transacti
from
oscar.core.loading
import
get_class
,
get_model
from
oscar.core.loading
import
get_class
,
get_model
from
ecommerce.extensions.checkout.mixins
import
EdxOrderPlacementMixin
from
ecommerce.extensions.checkout.mixins
import
EdxOrderPlacementMixin
from
ecommerce.extensions.checkout.utils
import
get_receipt_page_url
from
ecommerce.extensions.payment.exceptions
import
InvalidSignatureError
from
ecommerce.extensions.payment.exceptions
import
InvalidSignatureError
from
ecommerce.extensions.payment.processors.cybersource
import
Cybersource
from
ecommerce.extensions.payment.processors.cybersource
import
Cybersource
from
ecommerce.extensions.payment.processors.paypal
import
Paypal
from
ecommerce.extensions.payment.processors.paypal
import
Paypal
...
@@ -225,7 +226,10 @@ class PaypalPaymentExecutionView(EdxOrderPlacementMixin, View):
...
@@ -225,7 +226,10 @@ class PaypalPaymentExecutionView(EdxOrderPlacementMixin, View):
if
not
basket
:
if
not
basket
:
return
redirect
(
self
.
payment_processor
.
error_url
)
return
redirect
(
self
.
payment_processor
.
error_url
)
receipt_url
=
u'{}?orderNum={}'
.
format
(
self
.
payment_processor
.
receipt_url
,
basket
.
order_number
)
receipt_url
=
get_receipt_page_url
(
order_number
=
basket
.
order_number
,
site_configuration
=
basket
.
site
.
siteconfiguration
)
try
:
try
:
with
transaction
.
atomic
():
with
transaction
.
atomic
():
...
...
ecommerce/extensions/voucher/utils.py
View file @
5f5c0981
...
@@ -16,6 +16,7 @@ import pytz
...
@@ -16,6 +16,7 @@ import pytz
from
ecommerce.core.url_utils
import
get_ecommerce_url
from
ecommerce.core.url_utils
import
get_ecommerce_url
from
ecommerce.extensions.api
import
exceptions
from
ecommerce.extensions.api
import
exceptions
from
ecommerce.extensions.offer.utils
import
get_discount_percentage
,
get_discount_value
from
ecommerce.invoice.models
import
Invoice
from
ecommerce.invoice.models
import
Invoice
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -479,17 +480,17 @@ def get_voucher_discount_info(benefit, price):
...
@@ -479,17 +480,17 @@ def get_voucher_discount_info(benefit, price):
if
benefit
.
type
==
Benefit
.
PERCENTAGE
:
if
benefit
.
type
==
Benefit
.
PERCENTAGE
:
return
{
return
{
'discount_percentage'
:
benefit_value
,
'discount_percentage'
:
benefit_value
,
'discount_value'
:
benefit_value
*
price
/
100.0
,
'discount_value'
:
get_discount_value
(
discount_percentage
=
benefit_value
,
product_price
=
price
)
,
'is_discounted'
:
True
if
benefit
.
value
<
100
else
False
'is_discounted'
:
True
if
benefit
.
value
<
100
else
False
}
}
else
:
else
:
discount_percentage
=
benefit_value
/
price
*
100.0
discount_percentage
=
get_discount_percentage
(
discount_value
=
benefit_value
,
product_price
=
price
)
if
discount_percentage
>
100
:
if
discount_percentage
>
100
:
discount_percentage
=
100.00
discount_percentage
=
100.00
discount_value
=
price
discount_value
=
price
else
:
else
:
discount_percentage
=
float
(
discount_percentage
)
discount_percentage
=
discount_percentage
discount_value
=
benefit
.
value
discount_value
=
benefit
_
value
return
{
return
{
'discount_percentage'
:
discount_percentage
,
'discount_percentage'
:
discount_percentage
,
'discount_value'
:
float
(
discount_value
),
'discount_value'
:
float
(
discount_value
),
...
...
ecommerce/settings/_oscar.py
View file @
5f5c0981
...
@@ -104,7 +104,7 @@ PAYMENT_PROCESSORS = (
...
@@ -104,7 +104,7 @@ PAYMENT_PROCESSORS = (
'ecommerce.extensions.payment.processors.paypal.Paypal'
,
'ecommerce.extensions.payment.processors.paypal.Paypal'
,
)
)
PAYMENT_PROCESSOR_RECEIPT_PATH
=
'/c
ommerce/c
heckout/receipt/'
PAYMENT_PROCESSOR_RECEIPT_PATH
=
'/checkout/receipt/'
PAYMENT_PROCESSOR_CANCEL_PATH
=
'/checkout/cancel-checkout/'
PAYMENT_PROCESSOR_CANCEL_PATH
=
'/checkout/cancel-checkout/'
PAYMENT_PROCESSOR_ERROR_PATH
=
'/checkout/error/'
PAYMENT_PROCESSOR_ERROR_PATH
=
'/checkout/error/'
...
...
ecommerce/settings/base.py
View file @
5f5c0981
...
@@ -503,7 +503,7 @@ CELERY_ALWAYS_EAGER = False
...
@@ -503,7 +503,7 @@ CELERY_ALWAYS_EAGER = False
THEME_SCSS
=
'sass/themes/default.scss'
THEME_SCSS
=
'sass/themes/default.scss'
# Path to the receipt page
# Path to the receipt page
RECEIPT_PAGE_PATH
=
'/c
ommerce/c
heckout/receipt/'
RECEIPT_PAGE_PATH
=
'/checkout/receipt/'
# URL for Course Catalog service
# URL for Course Catalog service
COURSE_CATALOG_API_URL
=
'http://localhost:8008/api/v1/'
COURSE_CATALOG_API_URL
=
'http://localhost:8008/api/v1/'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment