Skip to content

Commit

Permalink
Add rudimentary (MAC-Failing) refund support
Browse files Browse the repository at this point in the history
  • Loading branch information
pc-coholic committed Apr 12, 2022
1 parent 6a46119 commit f44fadc
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 31 deletions.
56 changes: 55 additions & 1 deletion pretix_computop/payment.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import hashlib
import importlib
import json
import urllib
from base64 import b16encode, b16decode
from collections import OrderedDict
from decimal import Decimal
from urllib.parse import urlencode

import requests
from Crypto.Hash import SHA256, HMAC
from Crypto.Util import Padding
from django import forms
Expand All @@ -19,7 +21,7 @@

from pretix.base.decimal import round_decimal
from pretix.base.forms import SecretKeySettingsField
from pretix.base.models import Event, OrderPayment, Order
from pretix.base.models import Event, OrderPayment, Order, OrderRefund
from pretix.base.payment import BasePaymentProvider
from pretix.base.settings import SettingsSandbox
from pretix.multidomain.urlreverse import build_absolute_uri
Expand Down Expand Up @@ -197,6 +199,54 @@ def execute_payment(self, request: HttpRequest, payment: OrderPayment) -> str:
payment.save(update_fields=['info'])
return self.apiurl + '?' + urlencode(payload)

def payment_refund_supported(self, payment: OrderPayment) -> bool:
if 'PayID' in payment.info:
return True
return False

def payment_partial_refund_supported(self, payment: OrderPayment) -> bool:
if 'PayID' in payment.info:
return True
return False

def execute_refund(self, refund: OrderRefund):
data = {
'MerchantID': self.settings.get('merchant_id'),
'Amount': self._decimal_to_int(refund.amount),
'Currency': self.event.currency,
'MAC': self._calculate_hmac(
transaction_id=refund.full_id,
amount_or_status=str(self._decimal_to_int(refund.amount)),
currency_or_code=self.event.currency),
'PayID': refund.payment.info_data['PayID'][0],
'TransID': refund.full_id,
'RefNr': refund.full_id,
'OrderDesc': 'Order {}-{}'.format(self.event.slug.upper(), refund.full_id),
}
print(data)
encrypted_data = self._encrypt(urlencode(data))
payload = {
'MerchantID': self.settings.get('merchant_id'),
'Len': encrypted_data[1],
'Data': encrypted_data[0],
}

req = requests.post(
"https://www.computop-paygate.com/credit.aspx",
data=payload,
)

parsed = urllib.parse.parse_qs(req.text)
processed = self.parse_data(parsed['Data'][0])
refund.info = json.dumps(processed)
refund.save(update_fields=['info'])

if refund.info_data['Status'][0] == 'FAILED':
refund.state = OrderRefund.REFUND_STATE_FAILED
refund.save(update_fields=['state'])
elif refund.info_data['Status'][0] == 'AUTHORIZED':
refund.done()

def check_hash(self, payload_parsed):
mid = payload_parsed['mid'][0]
mac = str(payload_parsed['MAC'][0]).lower().rstrip()
Expand All @@ -222,3 +272,7 @@ def get_paytypes(self):
return "|".join(paytypes)
else:
return self.method

def parse_data(self, data):
payload = self.decrypt(str(data))
return urllib.parse.parse_qs(payload)
52 changes: 22 additions & 30 deletions pretix_computop/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import hashlib
import json
import urllib.parse

from cached_property import cached_property
from django.contrib import messages
Expand Down Expand Up @@ -51,49 +50,42 @@ def _redirect_to_order(self):
def pprov(self):
return self.payment.payment_provider

def _handle_data(self, data):
try:
response = self._parse_data(data)
except TypeError as err:
self.payment.fail({'decryption_error': err, 'request': self.payment.info})
except ValueError as err:
self.payment.fail({'decryption_error': err, 'request': self.payment.info})
else:
if self.pprov.check_hash(response):
self.payment.info = json.dumps({'request': self.payment.info, 'response': response})
self.payment.save(update_fields=['info'])
self._set_status_code(response['Code'][0], response)

def _parse_data(self, data):
payload = self.pprov.decrypt(data)
return urllib.parse.parse_qs(payload)

def _set_status_code(self, code, response):
if code == '00000000':
self.payment.confirm()
else:
messages.error(self.request, _('Your payment failed. Please try again.'))
self.payment.fail(info={'request': self.payment.info, 'response': response})


@method_decorator(csrf_exempt, name='dispatch')
class ReturnView(ComputopOrderView, View):
template_name = 'pretix_computop/return.html'

def post(self, request, *args, **kwargs):
if request.POST['Data']:
data = str(request.POST.get('Data'))
self._handle_data(data)
response = self.parse_data(request.POST.get('Data'))
if self.check_hash(response):
self.payment.info = json.dumps(response)
self.payment.save(update_fields=['info'])

if response['Code'][0] == '00000000':
self.payment.confirm()
else:
messages.error(request, _('Your payment failed. Please try again.'))
self.payment.fail(info=response)
return self._redirect_to_order()

def get(self, request, *args, **kwargs):
return self._redirect_to_order()


@method_decorator(csrf_exempt, name='dispatch')
class NotifyView(ReturnView, ComputopOrderView, View):
class NotifyView(ComputopOrderView, View):
template_name = 'pretix_computop/return.html'

def post(self, request, *args, **kwargs):
if request.POST['Data']:
data = str(request.POST.get('Data'))
self._handle_data(data)
response = self.parse_data(request.POST.get('Data'))
if self.check_hash(response):
self.payment.info = json.dumps(response)
self.payment.save(update_fields=['info'])

if response['Code'][0] == '00000000':
self.payment.confirm()
else:
self.payment.fail(info=response)
return HttpResponse('[accepted]', status=200)

0 comments on commit f44fadc

Please sign in to comment.