Skip to content

Commit

Permalink
implement netaxept gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
nwolff committed Sep 16, 2019
1 parent f4f1a4d commit c89ce9d
Show file tree
Hide file tree
Showing 20 changed files with 525 additions and 3 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ This module provides implementations for the following payment-gateways:

[More Stripe information](docs/stripe.md)

### Netaxept
Implemented features:
- Authorization
- Capture
- Refund

[More Netaxept information](docs/netaxept.md)

## The example project
The source distribution includes an example project that lets one exercise
Expand Down
15 changes: 15 additions & 0 deletions docs/netaxept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Netaxept

## Configuration

In the PAYMENT_GATEWAYS setting, configure the netaxept connection params:

`merchant_id`, `secret`, `wsdl_url`, `terminal_url`, and `after_terminal_url`.

The production wsdl_url and terminal_url are:

`https://epayment.nets.eu/netaxept.svc?wsdl`

`https://epayment.nets.eu/Terminal/default.aspx`


21 changes: 20 additions & 1 deletion example_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def abspath(*args):

ROOT_URLCONF = 'urls'

DATETIME_FORMAT = "Y-m-d @ H:i:s e"

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
Expand All @@ -80,14 +82,16 @@ def abspath(*args):
DATETIME_FORMAT = 'Y-m-d @ H:i:s e'

# Enable specific currencies (djmoney)
CURRENCIES = ['USD', 'EUR', 'JPY', 'GBP', 'CAD', 'CHF']
CURRENCIES = ['USD', 'EUR', 'JPY', 'GBP', 'CAD', 'CHF', 'NOK']

DUMMY = 'dummy'
STRIPE = 'stripe'
NETAXEPT = 'netaxept'

CHECKOUT_PAYMENT_GATEWAYS = {
DUMMY: 'Dummy gateway',
STRIPE: 'Stripe',
NETAXEPT: 'Netaxept',
}

PAYMENT_GATEWAYS = {
Expand Down Expand Up @@ -117,4 +121,19 @@ def abspath(*args):
},
},
},
NETAXEPT: {
'module': 'payment.gateways.netaxept',
'config': {
'auto_capture': True,
'template_path': 'payment/netaxept.html',
'connection_params': {
'wsdl_url': os.environ.get('NETAXEPT_WSDL_URL') or 'https://epayment-test.bbs.no/netaxept.svc?wsdl',
'terminal_url': os.environ.get(
'NETAXEPT_TERMINAL_URL') or 'https://epayment-test.bbs.no/Terminal/default.aspx',
'after_terminal_url': os.environ.get('NETAXEPT_AFTER_TERMINAL_URL'),
'merchant_id': os.environ.get('NETAXEPT_MERCHANT_ID'),
'secret': os.environ.get('NETAXEPT_SECRET'),
}
}
},
}
18 changes: 18 additions & 0 deletions example_project/templates/netaxept/form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<html>

<head>
<title>Netaxept - Example - {{ title }}</title>
</head>

<body>

<h2> {{ title }} </h2>

<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit"/>
</form>

</body>
</html>
2 changes: 2 additions & 0 deletions example_project/templates/operation_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ <H3>Operations</H3>
<li><a href="{% url 'stripe_elements_token' payment.id %}">Authorize - Elements token</a></li>
<li><a href="{% url 'stripe_checkout' payment.id %}">Authorize - Checkout</a></li>
<li><a href="{% url 'stripe_payment_intents_manual_flow' payment.id %}">Authorize - Payment intents manual flow</a>
{% elif payment.gateway == 'netaxept' %}
<li><a href="{% url 'netaxept_register_and_authorize' payment.id %}">Register and Authorize</a></li>
{% endif %}

<li><a href="{% url 'capture' payment.id %}">Capture</a></li>
Expand Down
2 changes: 2 additions & 0 deletions example_project/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
from django.urls import path, include

import views
from views import netaxept
from views import stripe

example_urlpatterns = [
path('<payment_id>', views.view_payment, name='view_payment'),
path('<payment_id>/capture', views.capture, name='capture'),
path('stripe/', include(stripe.urls)),
path('netaxept/', include(netaxept.urls)),
]

urlpatterns = [
Expand Down
36 changes: 36 additions & 0 deletions example_project/views/netaxept.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Example views for interactive testing of payment with netaxept.
You should restrict access (maybe with 'staff_member_required') if you choose to add this to your urlconf.
"""
from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect, get_object_or_404
from django.urls import path
from structlog import get_logger

from payment import get_payment_gateway
from payment.gateways.netaxept import actions
from payment.gateways.netaxept import gateway_to_netaxept_config
from payment.gateways.netaxept.netaxept_protocol import get_payment_terminal_url
from payment.models import Payment

logger = get_logger()


def register_and_authorize(request: HttpRequest, payment_id: int) -> HttpResponse:
"""
Register the payment with netaxept, and take the user to the terminal page for payment authorization.
"""
logger.info('netaxept-register-and-authorize', payment_id=payment_id)

transaction_id = actions.register_payment(payment_id)

payment = get_object_or_404(Payment, id=payment_id)
payment_gateway, gateway_config = get_payment_gateway(payment.gateway)
netaxept_config = gateway_to_netaxept_config(gateway_config)
return redirect(get_payment_terminal_url(config=netaxept_config, transaction_id=transaction_id))


urls = [
path('register_and_authorize/<payment_id>', register_and_authorize, name='netaxept_register_and_authorize'),
]
3 changes: 3 additions & 0 deletions payment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class TransactionKind:
"""Represents the type of a transaction.
The following transactions types are possible:
- REGISTER - Some gateways require an initial register transaction, before authorizing.
- AUTH - an amount reserved against the customer's funding source. Money
does not change hands until the authorization is captured.
- VOID - a cancellation of a pending authorization or capture.
Expand All @@ -59,6 +60,7 @@ class TransactionKind:
- REFUND - full or partial return of captured funds to the customer.
"""

REGISTER = "register"
AUTH = "auth"
CAPTURE = "capture"
VOID = "void"
Expand All @@ -67,6 +69,7 @@ class TransactionKind:
# Which were authorized, but needs to be confirmed manually by staff
# eg. Braintree with "submit_for_settlement" enabled
CHOICES = [
(REGISTER, pgettext_lazy("transaction kind", "Registration")),
(AUTH, pgettext_lazy("transaction kind", "Authorization")),
(REFUND, pgettext_lazy("transaction kind", "Refund")),
(CAPTURE, pgettext_lazy("transaction kind", "Capture")),
Expand Down
78 changes: 78 additions & 0 deletions payment/gateways/netaxept/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import zeep
from structlog import get_logger

from .netaxept_protocol import NetaxeptConfig, NetaxeptOperation, process
from ... import OperationType
from ...interface import GatewayConfig, GatewayResponse, PaymentData

logger = get_logger()

operation_type_to_netaxept_op = {
OperationType.AUTH: NetaxeptOperation.AUTH,
OperationType.CAPTURE: NetaxeptOperation.CAPTURE,
OperationType.VOID: NetaxeptOperation.ANNUL,
OperationType.REFUND: NetaxeptOperation.CREDIT,
}


def get_client_token(**_):
""" Not implemented for netaxept gateway. """
pass


def authorize(payment_information: PaymentData,
config: GatewayConfig,
should_capture: bool = False) -> GatewayResponse:
raise NotImplementedError()


def process_payment(payment_information: PaymentData, config: GatewayConfig) -> GatewayResponse:
raise NotImplementedError()


def capture(payment_information: PaymentData, config: GatewayConfig) -> GatewayResponse:
return _op(payment_information, config, OperationType.CAPTURE)


def refund(payment_information: PaymentData, config: GatewayConfig) -> GatewayResponse:
return _op(payment_information, config, OperationType.REFUND)


def void(payment_information: PaymentData, config: GatewayConfig) -> GatewayResponse:
return _op(payment_information, config, OperationType.VOID)


def gateway_to_netaxept_config(gateway_config: GatewayConfig) -> NetaxeptConfig:
return NetaxeptConfig(**gateway_config.connection_params)


# XXX: Need to return a GatewayResponse and also handle exceptions
# XXX: logging?
def _op(payment_information: PaymentData, config: GatewayConfig, operation_type: OperationType) -> GatewayResponse:
logger.info('netaxept-op', payment_information=payment_information, operation_type=operation_type)
try:
process_result = process(
config=gateway_to_netaxept_config(config),
transaction_id=payment_information.token,
operation=operation_type_to_netaxept_op[operation_type])
print(process_result)
return GatewayResponse(
is_success=True,
kind=operation_type.value,
amount=payment_information.amount,
currency=payment_information.currency,
transaction_id=None,
error=None,
raw_response=vars(process_result)
)
except zeep.exceptions.Fault as exception: # XXX
logger.error('netaxept-op-error', exc_info=exception)
return GatewayResponse(
is_success=False,
kind=operation_type.value,
amount=payment_information.amount,
currency=payment_information.currency,
transaction_id=None,
error=str(exception),
raw_response={}
)
83 changes: 83 additions & 0 deletions payment/gateways/netaxept/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from django.db import transaction
from django.shortcuts import get_object_or_404
from structlog import get_logger

from payment import get_payment_gateway, TransactionKind
from payment.gateways.netaxept import netaxept_protocol, gateway_to_netaxept_config
from payment.models import Payment, Transaction

logger = get_logger()


class NetaxeptException(Exception):
def __str__(self):
return repr(self.msg)


class PaymentAlreadyRegistered(NetaxeptException):
msg = 'Payment already registered'


class PaymentNotAuthorized(NetaxeptException):
msg = 'Payment not authorized'


class PaymentRegistrationNotCompleted(NetaxeptException):
msg = 'Payment registration not completed'


def register_payment(payment_id: int) -> str:
"""
- Registers the payment with netaxept.
- Records a Transaction representing the registration.
- Stores the newly created netaxept transaction_id in the Payment.
:param payment_id: The id of a Payment object.
:return: The newly created netaxept transaction_id
"""
payment = get_object_or_404(Payment, id=payment_id)

if payment.token != '':
raise PaymentAlreadyRegistered()

_payment_gateway, gateway_config = get_payment_gateway(payment.gateway)
netaxept_config = gateway_to_netaxept_config(gateway_config)

transaction_id = netaxept_protocol.register(
config=netaxept_config,
order_number=payment_id,
amount=payment.total,
language='en')

# XXX: Should also create a transaction object in case of exception (but not update the payment transaction id).

with transaction.atomic():
Transaction.objects.create(
payment=payment,
kind=TransactionKind.REGISTER,
token=transaction_id,
is_success=True,
amount=payment.total,
error=None,
gateway_response={})

payment.token = transaction_id
payment.save()

logger.info('netaxept-register-payment', transaction_id=transaction_id)

return transaction_id


def create_auth_transaction(transaction_id: str, success: bool) -> None:
""" Record the outcome of a netaxept auth transaction. """
payment = Payment.objects.get(token=transaction_id)

Transaction.objects.create(
payment=payment,
kind=TransactionKind.AUTH,
token=transaction_id,
is_success=success,
amount=payment.total,
error=None,
gateway_response={})
Loading

0 comments on commit c89ce9d

Please sign in to comment.