Skip to content

Commit

Permalink
Rebasing Koa from juniper
Browse files Browse the repository at this point in the history
  • Loading branch information
Taimoor Ahmed authored and Taimoor Ahmed committed Sep 5, 2023
2 parents 327eec3 + da9f169 commit 809f14c
Show file tree
Hide file tree
Showing 13 changed files with 473 additions and 12 deletions.
4 changes: 4 additions & 0 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
{
name: 'js/views/cowpay',
exclude: ['js/common']
},
{
name: 'js/views/authorizenet',
exclude: ['js/common']
}
]
})
108 changes: 108 additions & 0 deletions ecommerce/extensions/payment/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,111 @@ def clean_basket(self):
Applicator().apply(basket, self.request.user, self.request)

return basket


class AuthorizenetPaymentForm(forms.Form):
"""
Payment form for Authorizenet with billing details.
This form captures the data necessary to complete a payment transaction with Authorizenet.
"""
def __init__(self, user, request, *args, **kwargs):
super(AuthorizenetPaymentForm, self).__init__(*args, **kwargs)
self.request = request
self.basket_has_enrollment_code_product = any(
line.product.is_enrollment_code_product for line in self.request.basket.all_lines()
)
update_basket_queryset_filter(self, user)

self.helper = FormHelper(self)
self.helper.layout = Layout(
Div('basket'),
Div(
Div('full_name'),
HTML('<p class="help-block-name"></p>'),
css_class='form-item col-md-12'
),
Div(
Div('card_number'),
HTML('<p class="help-block-card"></p>'),
css_class='form-item col-md-12'
),
Div(
Div('card_code', css_class='form-item col-md-4'),
Div('expiry_month', css_class='form-item col-md-4'),
Div('expiry_year', css_class='form-item col-md-4'),
HTML('<p class="help-block-expiry"></p>'),
css_class='row'
),
Div(
HTML('<input type="hidden" name="data_value" id="id_data_value" />'),
css_class='form-item col-md-12'
),
Div(
HTML('<input type="hidden" name="data_descriptor" id="id_data_descriptor" />'),
HTML('<div class="authorizenet-error"></div>'),
css_class='form-item col-md-12'
),
)

for bound_field in list(self):
# https://www.w3.org/WAI/tutorials/forms/validation/#validating-required-input
if hasattr(bound_field, 'field') and bound_field.field.required:
# Translators: This is a string added next to the name of the required
# fields on the payment form. For example, the first name field is
# required, so this would read "First name (required)".
self.fields[bound_field.name].label = _('{label} (required)').format(label=bound_field.label)
bound_field.field.widget.attrs['required'] = 'required'

if self.basket_has_enrollment_code_product and 'organization' not in self.fields:
# If basket has any enrollment code items then we will add an organization
# field next to "last_name."
self.fields['organization'] = forms.CharField(max_length=60, label=_('Organization (required)'))
organization_div = Div(
Div(
Div('organization'),
HTML('<p class="help-block"></p>'),
css_class='form-item col-md-6'
),
css_class='row'
)
self.helper.layout.fields.insert(list(self.fields.keys()).index('last_name') + 1, organization_div)
# Purchased on behalf of an enterprise or for personal use
self.fields[PURCHASER_BEHALF_ATTRIBUTE] = forms.BooleanField(
required=False,
label=_('I am purchasing on behalf of my employer or other professional organization')
)
purchaser_div = Div(
Div(
Div(PURCHASER_BEHALF_ATTRIBUTE),
HTML('<p class="help-block"></p>'),
css_class='form-item col-md-12'
),
css_class='row'
)
self.helper.layout.fields.insert(list(self.fields.keys()).index('organization') + 1, purchaser_div)

basket = forms.ModelChoiceField(
queryset=Basket.objects.all(),
widget=forms.HiddenInput(),
required=False,
error_messages={
'invalid_choice': _('There was a problem retrieving your basket. Refresh the page to try again.'),
}
)
full_name = forms.CharField(max_length=60, label=_('Full Name'))
card_number = forms.CharField(max_length=16, required=False, label=_('Card Number'))
card_code = forms.CharField(max_length=4, required=False, label=_('CVV'))
expiry_month = forms.CharField(max_length=60, required=False, label=_('Expiry Month (mm)'))
expiry_year = forms.CharField(max_length=60, required=False, label=_('Expiry Year (yy)'))
data_descriptor = forms.CharField(max_length=255)
data_value = forms.CharField(max_length=255)

def clean_basket(self):
basket = self.cleaned_data['basket']

if basket:
basket.strategy = self.request.strategy
Applicator().apply(basket, self.request.user, self.request)

return basket
171 changes: 169 additions & 2 deletions ecommerce/extensions/payment/processors/authorizenet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
""" AuthorizeNet payment processor. """
from __future__ import absolute_import, unicode_literals

import crum
import json
import logging
from urllib.parse import quote
Expand All @@ -11,8 +14,8 @@
)
from authorizenet.constants import constants
from django.urls import reverse
from oscar.apps.payment.exceptions import GatewayError
from oscar.core.loading import get_model
from oscar.apps.payment.exceptions import GatewayError, TransactionDeclined
from oscar.core.loading import get_class, get_model

from ecommerce.core.url_utils import get_ecommerce_url
from ecommerce.extensions.payment.exceptions import (
Expand All @@ -21,10 +24,12 @@
PaymentProcessorResponseNotFound,
RefundError
)
from ecommerce.extensions.payment.forms import AuthorizenetPaymentForm
from ecommerce.extensions.payment.processors import BaseClientSidePaymentProcessor, HandledProcessorResponse
from ecommerce.extensions.payment.utils import LxmlObjectJsonEncoder

logger = logging.getLogger(__name__)
Applicator = get_class('offer.applicator', 'Applicator')
PaymentProcessorResponse = get_model('payment', 'PaymentProcessorResponse')

AUTH_CAPTURE_TRANSACTION_TYPE = "authCaptureTransaction"
Expand Down Expand Up @@ -351,3 +356,165 @@ def issue_credit(self, order_number, basket, reference_number, amount, currency)
order_number)
logger.exception(msg)
raise RefundError(msg)


class AuthorizenetClient(BaseClientSidePaymentProcessor):
NAME = 'authorizenetclient'
template_name = 'payment/authorizenet.html'

def __init__(self, site):
"""
Constructs a new instance of the Authorizenet Client processor.
Raises:
KeyError: If no settings configured for this payment processor.
"""
super(AuthorizenetClient, self).__init__(site)
self.request = crum.get_current_request()
configuration = self.configuration
self.client_key = configuration['client_key']
self.base_url = configuration['base_url']
self.api_login_id = configuration['api_login_id']
self.transaction_key = configuration['transaction_key']
self.authorizenet_production_mode = configuration['production_mode']

@property
def authorizenet_form(self):
return AuthorizenetPaymentForm(
user=self.request.user,
request=self.request,
initial={'basket': self.request.basket},
label_suffix=''
)

def get_transaction_parameters(self, basket, request=None, **kwargs):
basket.strategy = self.request.strategy
Applicator().apply(basket, self.request.user, self.request)

merchant_auth = apicontractsv1.merchantAuthenticationType()
merchant_auth.name = self.api_login_id
merchant_auth.transactionKey = self.transaction_key

# Create the payment object for a payment nonce
opaque_data = apicontractsv1.opaqueDataType()
opaque_data.dataDescriptor = kwargs['data_descriptor']
opaque_data.dataValue = kwargs['data_value']

# Add the payment data to a paymentType object
payment_one = apicontractsv1.paymentType()
payment_one.opaqueData = opaque_data

# Create order information
order = apicontractsv1.orderType()
order.invoiceNumber = basket.order_number
order.description = '{} - {}: {}'.format(
basket.order_number,
basket.site.partner.name,
basket.all_lines().first().product.title
)

# Set the customer's Bill To address
owner = basket.owner
customer_address = apicontractsv1.customerAddressType()
customer_address.firstName = owner.first_name
customer_address.lastName = owner.last_name

# Set the customer's identifying information
customer_data = apicontractsv1.customerDataType()
customer_data.type = "individual"
customer_data.id = str(owner.id)
customer_data.email = owner.email

# Add values for transaction settings
duplicate_window_setting = apicontractsv1.settingType()
duplicate_window_setting.settingName = "duplicateWindow"
duplicate_window_setting.settingValue = "600"
settings = apicontractsv1.ArrayOfSetting()
settings.setting.append(duplicate_window_setting)

# Create a transactionRequestType object and add the previous objects to it
transaction_request = apicontractsv1.transactionRequestType()
transaction_request.transactionType = "authCaptureTransaction"
transaction_request.amount = basket.total_incl_tax
transaction_request.order = order
transaction_request.payment = payment_one
transaction_request.billTo = customer_address
transaction_request.customer = customer_data
transaction_request.transactionSettings = settings

# Assemble the complete transaction request
create_transaction_request = apicontractsv1.createTransactionRequest()
create_transaction_request.merchantAuthentication = merchant_auth
create_transaction_request.refId = basket.order_number
create_transaction_request.transactionRequest = transaction_request

# Create the controller and get response
create_transaction_controller = createTransactionController(create_transaction_request)
if self.authorizenet_production_mode:
create_transaction_controller.setenvironment(constants.PRODUCTION)

create_transaction_controller.execute()

response = create_transaction_controller.getresponse()

if response is not None:
if response.messages.resultCode == 'Ok':
if hasattr(response.transactionResponse, 'messages'):
logger.info('Successfully created transaction with Transaction ID: %s' % response.transactionResponse.transId)
logger.info('Transaction Response Code: %s' % response.transactionResponse.responseCode)
logger.info('Message Code: %s' % response.transactionResponse.messages.message[0].code)
logger.info('Auth Code: %s' % response.transactionResponse.authCode)
logger.info('Description: %s' % response.transactionResponse.messages.message[0].description)
else:
if hasattr(response.transactionResponse, 'errors'):
logger.info('Error Code: %s' % str(response.transactionResponse.errors.error[0].errorCode))
logger.info('Error Message: %s' % response.transactionResponse.errors.error[0].errorText)
self.handle_processor_response(response, basket)
raise TransactionDeclined(
'Payment error: {}'.format(
response.transactionResponse.errors.error[0].errorText
)
)
else:
if hasattr(response, 'transactionResponse') and hasattr(response.transactionResponse, 'errors'):
logger.info('Error Code: %s' % str(response.transactionResponse.errors.error[0].errorCode))
logger.info('Error Message: %s' % response.transactionResponse.errors.error[0].errorText)
self.handle_processor_response(response, basket)
raise TransactionDeclined(
'Payment error: {}'.format(
response.transactionResponse.errors.error[0].errorText
)
)
else:
logger.info('Error Code: %s' % response.messages.message[0]['code'].text)
logger.info('Error Message: %s' % response.messages.message[0]['text'].text)
self.handle_processor_response(response, basket)
raise TransactionDeclined(
'Payment Gateway error: {}'.format(
response.messages.message[0]['text'].text
)
)

return response

def handle_processor_response(self, response, basket=None):
currency = basket.currency
transaction_id = response.transactionResponse.transId if hasattr(response.transactionResponse, 'transId') else None
transaction_dict = LxmlObjectJsonEncoder().encode(response)

self.record_processor_response(transaction_dict, transaction_id=transaction_id, basket=basket)
logger.info('Successfully created Authorizenet charge [%s] for basket [%d].', 'merchant_id', basket.id)

total = basket.total_incl_tax
card_type = self.NAME

return HandledProcessorResponse(
transaction_id=transaction_id,
total=total,
currency=currency,
card_number='XXXX',
card_type=card_type
)

def issue_credit(self, order_number, basket, reference_number, amount, currency):
raise NotImplementedError('Authorizenet payment processor does not support refunds.')
4 changes: 2 additions & 2 deletions ecommerce/extensions/payment/processors/cowpay.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def fawry_form(self):

@property
def iframe_token(self):
cowpay_url = urljoin(self.base_url, '/api/v1/iframe/token')
cowpay_url = urljoin(self.base_url, '/api/v2/charge/card/init')
headers = self._get_request_header()
basket = self.request.basket
payload = self._get_request_payload(
Expand All @@ -104,7 +104,7 @@ def receipt_page_url(self):
return get_receipt_page_url(site.siteconfiguration, order_number=basket.order_number)

def get_transaction_parameters(self, basket, request=None, **kwargs):
cowpay_url = urljoin(self.base_url, '/api/v1/charge/fawry')
cowpay_url = urljoin(self.base_url, '/api/v2/charge/fawry')
headers = self._get_request_header()
data = kwargs.get('form_data')
if data:
Expand Down
1 change: 1 addition & 0 deletions ecommerce/extensions/payment/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
AUTHORIZENET_URLS = [
url(r'^notification/$', authorizenet.AuthorizeNetNotificationView.as_view(), name='authorizenet_notifications'),
url(r'^redirect/$', authorizenet.handle_redirection, name='redirect'),
url(r'^submit/$', authorizenet.AuthorizenetClientView.as_view(), name='submit'),
]

COWPAY_URLS = [
Expand Down
Loading

0 comments on commit 809f14c

Please sign in to comment.