Skip to content

Commit

Permalink
query
Browse files Browse the repository at this point in the history
  • Loading branch information
nwolff committed Oct 14, 2019
1 parent 1eb0315 commit a3dac71
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 29 deletions.
15 changes: 15 additions & 0 deletions example_project/templates/netaxept/query_result.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<html>
<body>

<p>Annuled: {{ query_response.annuled }}</p>

<p>Authorized: {{ query_response.authorized }}</p>

<p>Status code: {{ query_response.raw_response.status_code }}</p>

<p>Raw response:
<pre> {{ query_response.raw_response.text }} </pre>
</p>

</body>
</html>
3 changes: 3 additions & 0 deletions example_project/templates/operation_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ <H3>Operations</H3>
<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>
{% if payment.token %}
<li><a href="{% url 'netaxept_query' payment.id %}">Query</a></li>
{% endif %}
{% endif %}
<li><a href="{%url 'admin:payment_payment_change' payment.id %}">See payment in admin</a></li>
</ul>
Expand Down
23 changes: 20 additions & 3 deletions example_project/views/netaxept.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
"""
Example views for interactive testing of payment with netaxept.
"""

from django.http import HttpRequest
from django.http import HttpResponse
from django.shortcuts import redirect, get_object_or_404
from django.template.response import TemplateResponse
from django.urls import path
from django.views.decorators.http import require_GET
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.gateways.netaxept import netaxept_protocol
from payment.models import Payment

logger = get_logger()
Expand All @@ -29,7 +31,7 @@ def register_and_authorize(request: HttpRequest, payment_id: int) -> HttpRespons
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))
return redirect(netaxept_protocol.get_payment_terminal_url(config=netaxept_config, transaction_id=transaction_id))


@require_GET
Expand All @@ -48,7 +50,7 @@ def after_terminal(request):
"""
transaction_id = request.GET['transactionId']
response_code = request.GET['responseCode']
logger.info('netaxept-webhook', transaction_id=transaction_id, response_code=response_code)
logger.info('netaxept-after-terminal', transaction_id=transaction_id, response_code=response_code)

success = (response_code == 'OK')

Expand All @@ -60,7 +62,22 @@ def after_terminal(request):
return HttpResponse('response code {}'.format(response_code))


def query(request: HttpRequest, payment_id: int) -> HttpResponse:
"""
Queries netaxept for the status of the transaction related to the given payment_id
"""
logger.info('netaxept-query', payment_id=payment_id)
payment = get_object_or_404(Payment, id=payment_id)
transaction_id = payment.token
payment_gateway, gateway_config = get_payment_gateway(payment.gateway)
netaxept_config = gateway_to_netaxept_config(gateway_config)
query_response = netaxept_protocol.query(config=netaxept_config, transaction_id=transaction_id)
return TemplateResponse(request, 'netaxept/query_result.html',
{'payment': payment, 'query_response': query_response})


urls = [
path('register_and_authorize/<payment_id>', register_and_authorize, name='netaxept_register_and_authorize'),
path('after_terminal', after_terminal, name='netaxept_after_terminal'),
path('query/<payment_id>', query, name='netaxept_query'),
]
3 changes: 2 additions & 1 deletion payment/gateways/netaxept/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ def register_payment(payment_id: int) -> str:
config=netaxept_config,
order_number=payment_id,
amount=payment.total,
language='en')
language='en',
customer_email=payment.customer_email)
except NetaxeptProtocolError as exception:
Transaction.objects.create(
payment=payment,
Expand Down
64 changes: 51 additions & 13 deletions payment/gateways/netaxept/netaxept_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
API details: https://shop.nets.eu/web/partners/appi
Test card numbers: https://shop.nets.eu/web/partners/test-cards
"""
from dataclasses import dataclass
from decimal import Decimal
from enum import Enum
from typing import Optional, Union, Dict, Any
from urllib.parse import urlencode, urljoin

import requests
import xmltodict
from dataclasses import dataclass
from moneyed import Money
from structlog import get_logger
from typing import Optional, Union, Dict

logger = get_logger()

Expand Down Expand Up @@ -54,11 +54,12 @@ def __init__(self, error: str, raw_response: Dict[str, str]):
@dataclass
class RegisterResponse:
transaction_id: str
raw_response: Dict[str, str]
raw_response: Dict[str, Any]


def register(config: NetaxeptConfig, amount: Money, order_number: Union[str, int],
language: Optional[str] = None, description: Optional[str] = None) -> RegisterResponse:
language: Optional[str] = None, description: Optional[str] = None,
customer_email: Optional[str] = None) -> RegisterResponse:
"""
Registering a payment is the first step for netaxept, before taking the user to the netaxept
terminal hosted page.
Expand All @@ -70,12 +71,13 @@ def register(config: NetaxeptConfig, amount: Money, order_number: Union[str, int
:param order_number: An alphanumerical string identifying the payment. 32 chars max (letters and numbers)
:param language: The iso639-1 code of the language in which the terminal should be displayed.
:param description: A text that will be displayed in the netaxept admin (but not to the user).
:param customer_email: The email of the customer, can then be seen in the netaxept admin portal.
:return: a RegisterResponse
:raises: NetaxeptProtocolError
"""

logger.info('netaxept-register', amount=amount, order_number=order_number, language=language,
description=description)
description=description, customer_email=customer_email)

params = {
'merchantId': config.merchant_id,
Expand All @@ -94,10 +96,11 @@ def register(config: NetaxeptConfig, amount: Money, order_number: Union[str, int
'redirectUrl': config.after_terminal_url
}

response = requests.post(url=urljoin(config.base_url, 'Netaxept/Register.aspx'),
data=params)
raw_response = _build_raw_response(response)
if customer_email is not None:
params['customerEmail'] = customer_email

response = requests.post(url=urljoin(config.base_url, 'Netaxept/Register.aspx'), data=params)
raw_response = _build_raw_response(response)
logger.info('netaxept-register', amount=amount, order_number=order_number, language=language,
description=description, raw_response=raw_response)

Expand All @@ -115,7 +118,7 @@ def register(config: NetaxeptConfig, amount: Money, order_number: Union[str, int
@dataclass
class ProcessResponse:
response_code: str
raw_response: Dict[str, str]
raw_response: Dict[str, Any]


def process(config: NetaxeptConfig, transaction_id: str, operation: NetaxeptOperation,
Expand All @@ -138,11 +141,8 @@ def process(config: NetaxeptConfig, transaction_id: str, operation: NetaxeptOper
'transactionAmount': _decimal_to_netaxept_amount(amount),
}

response = requests.post(url=urljoin(config.base_url, 'Netaxept/Process.aspx'),
data=params)

response = requests.post(url=urljoin(config.base_url, 'Netaxept/Process.aspx'), data=params)
raw_response = _build_raw_response(response)

logger.info('netaxept-process-response', transaction_id=transaction_id, operation=operation.value,
amount=amount, raw_response=raw_response)

Expand All @@ -162,6 +162,44 @@ def get_payment_terminal_url(config: NetaxeptConfig, transaction_id: str) -> str
return '{}?{}'.format(urljoin(config.base_url, 'Terminal/default.aspx'), qs)


@dataclass
class QueryResponse:
""" The query response is a very complete object, but we just model what's interesting for our use-case.
(The complete response is captured in the raw_response)
"""
annuled: bool
authorized: bool
raw_response: Dict[str, Any]


def query(config: NetaxeptConfig, transaction_id: str) -> QueryResponse:
logger.info('netaxept-query', transaction_id=transaction_id)

params = {
'merchantId': config.merchant_id,
'token': config.secret,
'transactionId': transaction_id,
}

response = requests.post(url=urljoin(config.base_url, 'Netaxept/Query.aspx'), data=params)
raw_response = _build_raw_response(response)
logger.info('netaxept-query-response', transaction_id=transaction_id, raw_response=raw_response)
if response.status_code == requests.codes.ok:
d = xmltodict.parse(response.text)
if 'PaymentInfo' in d:
summary = d['PaymentInfo']['Summary']
annuled = summary['Annuled'] == 'true'
authorized = summary['Authorized'] == 'true'
return QueryResponse(
annuled=annuled,
authorized=authorized,
raw_response=raw_response
)
elif 'Exception' in d:
raise NetaxeptProtocolError(d['Exception']['Error']['Message'], raw_response)
raise NetaxeptProtocolError(response.reason, raw_response)


def _decimal_to_netaxept_amount(decimal_amount: Decimal) -> int:
""" Return the netaxept representation of the decimal representation of the amount. """
return int((decimal_amount * 100).to_integral_value())
Expand Down
112 changes: 100 additions & 12 deletions tests/gateways/test_netaxept.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from payment.gateways.netaxept import gateway_to_netaxept_config, capture, refund
from payment.gateways.netaxept.netaxept_protocol import NetaxeptConfig, get_payment_terminal_url, \
_iso6391_to_netaxept_language, _money_to_netaxept_amount, _money_to_netaxept_currency, register, RegisterResponse, \
NetaxeptProtocolError, process, ProcessResponse, NetaxeptOperation
NetaxeptProtocolError, process, ProcessResponse, NetaxeptOperation, query, QueryResponse
from payment.interface import GatewayResponse
from payment.utils import create_payment_information

Expand Down Expand Up @@ -79,15 +79,16 @@ def it_should_register(requests_post):
<TransactionId>7624b99699f344e3b6da9884d20f0b27</TransactionId>
</RegisterResponse>""")
requests_post.return_value = mock_response
register_response = register(_netaxept_config, amount=Money(10, 'CHF'), order_number='123')
register_response = register(config=_netaxept_config, amount=Money(10, 'CHF'), order_number='123',
customer_email='nwolff@gmail.com')
assert register_response == RegisterResponse(
transaction_id='7624b99699f344e3b6da9884d20f0b27',
raw_response=asdict(mock_response))
requests_post.assert_called_with(
url='https://test.epayment.nets.eu/Netaxept/Register.aspx',
data={'merchantId': '123456', 'token': 'supersekret', 'description': None, 'orderNumber': '123',
'amount': 1000, 'currencyCode': 'CHF', 'autoAuth': True, 'terminalSinglePage': True,
'language': None, 'redirectUrl': 'http://localhost'})
'language': None, 'customerEmail': 'nwolff@gmail.com', 'redirectUrl': 'http://localhost'})


@patch('requests.post')
Expand Down Expand Up @@ -176,6 +177,93 @@ def it_should_handle_process_failure(requests_post):
'transactionId': '1111111111114cf693a1cf86123e0d8f', 'transactionAmount': 1000})


@patch('requests.post')
def it_should_query(requests_post):
mock_response = MockResponse(
status_code=200,
url='https://test.epayment.nets.eu/Netaxept/Query.aspx',
encoding='ISO-8859-1',
reason='OK',
text="""<?xml version="1.0" encoding="utf-8"?>
<PaymentInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MerchantId>11111111</MerchantId>
<QueryFinished>2019-10-14T10:15:07.2677951+02:00</QueryFinished>
<TransactionId>1111111111114cf693a1cf86123e0d8f</TransactionId>
<OrderInformation>
<Amount>700</Amount>
<Currency>NOK</Currency>
<OrderNumber>7</OrderNumber>
<OrderDescription> </OrderDescription>
<Fee>0</Fee>
<RoundingAmount>0</RoundingAmount>
<Total>700</Total>
<Timestamp>2019-09-11T16:30:06.967</Timestamp>
</OrderInformation>
<TerminalInformation>
<CustomerEntered>2019-09-11T16:30:08.513</CustomerEntered>
<CustomerRedirected>2019-09-11T16:30:24.903</CustomerRedirected>
<Browser>Chrome-Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36</Browser>
</TerminalInformation>
<CustomerInformation>
<Email />
<IP>85.218.56.162</IP>
<PhoneNumber />
<CustomerNumber />
<FirstName />
<LastName />
<Address1 />
<Address2 />
<Postcode />
<Town />
<Country />
<SocialSecurityNumber />
<CompanyName />
<CompanyRegistrationNumber />
</CustomerInformation>
<Summary>
<AmountCaptured>700</AmountCaptured>
<AmountCredited>0</AmountCredited>
<Annulled>false</Annulled>
<Annuled>false</Annuled>
<Authorized>true</Authorized>
<AuthorizationId>169337</AuthorizationId>
</Summary>
<CardInformation>
<Issuer>Visa</Issuer>
<IssuerCountry>NO</IssuerCountry>
<MaskedPAN>492500******0004</MaskedPAN>
<PaymentMethod>Visa</PaymentMethod>
<ExpiryDate>2301</ExpiryDate>
<IssuerId>3</IssuerId>
</CardInformation>
<History>
<TransactionLogLine>
<DateTime>2019-09-11T16:30:06.967</DateTime>
<Operation>Register</Operation>
</TransactionLogLine>
<TransactionLogLine>
<DateTime>2019-09-11T16:30:24.81</DateTime>
<Description>127.0.0.1: Auto AUTH</Description>
<Operation>Auth</Operation>
<BatchNumber>672</BatchNumber>
</TransactionLogLine>
</History>
<ErrorLog />
<AuthenticationInformation />
<AvtaleGiroInformation />
<SecurityInformation>
<CustomerIPCountry>CH</CustomerIPCountry>
<IPCountryMatchesIssuingCountry>false</IPCountryMatchesIssuingCountry>
</SecurityInformation>
</PaymentInfo>""")
requests_post.return_value = mock_response
query_response = query(config=_netaxept_config, transaction_id='233abb21f18b47dc98469fb9000b1f21')
assert query_response == QueryResponse(
annuled=False,
authorized=True,
raw_response=asdict(mock_response))


##############################################################################
# SPI tests

Expand All @@ -199,9 +287,9 @@ def it_should_capture(process, netaxept_authorized_payment):
'text': '<?xml version="1.0" encoding="utf-8"?>\n <ProcessResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">\n <BatchNumber>675</BatchNumber>\n <ExecutionTime>2019-09-16T17:31:00.7593672+02:00</ExecutionTime>\n <MerchantId>123456</MerchantId>\n <Operation>CAPTURE</Operation>\n <ResponseCode>OK</ResponseCode>\n <TransactionId>1111111111114cf693a1cf86123e0d8f</TransactionId>\n </ProcessResponse>'})
process.return_value = mock_process_response
payment_info = create_payment_information(
payment=netaxept_authorized_payment,
payment_token='1111111111114cf693a1cf86123e0d8f',
amount=Money(10, 'CHF'))
payment=netaxept_authorized_payment,
payment_token='1111111111114cf693a1cf86123e0d8f',
amount=Money(10, 'CHF'))
capture_result = capture(config=_gateway_config, payment_information=payment_info)
assert capture_result == GatewayResponse(
is_success=True,
Expand All @@ -226,9 +314,9 @@ def it_should_capture(process, netaxept_authorized_payment):
def it_should_not_capture_when_protocol_error(process, netaxept_authorized_payment):
process.side_effect = NetaxeptProtocolError(error='some error', raw_response={})
payment_info = create_payment_information(
payment=netaxept_authorized_payment,
payment_token='1111111111114cf693a1cf86123e0d8f',
amount=Money(10, 'CHF'))
payment=netaxept_authorized_payment,
payment_token='1111111111114cf693a1cf86123e0d8f',
amount=Money(10, 'CHF'))
capture_result = capture(config=_gateway_config, payment_information=payment_info)
assert capture_result == GatewayResponse(
is_success=False,
Expand Down Expand Up @@ -260,9 +348,9 @@ def it_should_refund(process, netaxept_authorized_payment):
'text': '<?xml version="1.0" encoding="utf-8"?>\n <ProcessResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">\n <BatchNumber>675</BatchNumber>\n <ExecutionTime>2019-09-16T17:31:00.7593672+02:00</ExecutionTime>\n <MerchantId>123456</MerchantId>\n <Operation>REFUND</Operation>\n <ResponseCode>OK</ResponseCode>\n <TransactionId>1111111111114cf693a1cf86123e0d8f</TransactionId>\n </ProcessResponse>'})
process.return_value = mock_process_response
payment_info = create_payment_information(
payment=netaxept_authorized_payment,
payment_token='1111111111114cf693a1cf86123e0d8f',
amount=Money(10, 'CHF'))
payment=netaxept_authorized_payment,
payment_token='1111111111114cf693a1cf86123e0d8f',
amount=Money(10, 'CHF'))
capture_result = refund(config=_gateway_config, payment_information=payment_info)
assert capture_result == GatewayResponse(
is_success=True,
Expand Down

0 comments on commit a3dac71

Please sign in to comment.