diff --git a/sofort/client.py b/sofort/client.py
index 4b24c85..25e85c5 100644
--- a/sofort/client.py
+++ b/sofort/client.py
@@ -7,7 +7,7 @@
from sofort.internals import Config, as_list
from sofort import model
-from _version import __version__
+from sofort._version import __version__
API_URL = 'https://api.sofort.com/api/xml'
TRANSACTION_ID = '-TRANSACTION-'
@@ -71,6 +71,13 @@ def payment(self, amount, **kwargs):
return self._request(sofort.xml.multipay(params), params)
+ def refunds(self, sender, refunds):
+ request_body = sofort.xml.refunds_by_params({
+ 'sender': sender,
+ 'refunds': refunds
+ })
+ return self._request(request_body)
+
def details(self, transaction_ids):
request_body = sofort.xml.transaction_request_by_params({
'transaction': as_list(transaction_ids)
diff --git a/sofort/model.py b/sofort/model.py
index d538d85..ace2745 100644
--- a/sofort/model.py
+++ b/sofort/model.py
@@ -14,11 +14,11 @@
def response(xmlstr):
result = xmltodict.parse(xmlstr)
# only one root element is allowed in XML
- for root, value in result.iteritems():
+ for root, value in result.items():
if value is None:
return None
factory = factories[root]
- return factory(value)
+ return factory(value, strict=False)
class ForcedListType(ListType):
@@ -119,14 +119,33 @@ class TransactionDetailsModel(Model):
ModelType(StatusHistoryItemModel))
-def transaction_list(transactions):
- return [TransactionDetailsModel(transact)
+class RefundModel(Model):
+ recipient = ModelType(BankAccountModel)
+ transaction = StringType()
+ amount = DecimalType()
+ comment = StringType()
+ reason_1 = StringType()
+ reason_2 = StringType()
+ time = Iso8601DateTimeType()
+ partial_refund_id = StringType()
+ status = StringType()
+ errors = SofortListType('error', ModelType(ErrorModel))
+
+class RefundsModel(Model):
+ sender = ModelType(BankAccountModel)
+ title = StringType()
+ pain = StringType()
+ refund = ModelType(RefundModel)
+
+
+def transaction_list(transactions, strict=False):
+ return [TransactionDetailsModel(transact, strict=strict)
for transact
in as_list(transactions['transaction_details'])]
-def error_handler(data):
- root = RootErrorsModel(data)
+def error_handler(data, strict=False):
+ root = RootErrorsModel(data, strict=strict)
errors = [RequestError(**error_item) for error_item in root.error]
if root.su:
errors.extend([RequestError(**error_item)
@@ -139,4 +158,5 @@ def error_handler(data):
'errors': error_handler,
'transactions': transaction_list,
'new_transaction': NewTransactionModel,
+ 'refunds': RefundsModel,
}
diff --git a/sofort/xml.py b/sofort/xml.py
index ff76d70..6820af4 100644
--- a/sofort/xml.py
+++ b/sofort/xml.py
@@ -59,7 +59,7 @@ def multipay(config):
def transaction_request_by_params(params):
root = etree.Element('transaction_request')
root.set('version', '2')
- for name, value in params.iteritems():
+ for name, value in params.items():
if name == 'transaction':
for transaction_id in value:
etree.SubElement(root, 'transaction').text = transaction_id
@@ -68,6 +68,27 @@ def transaction_request_by_params(params):
return etree.tostring(root)
+def refunds_by_params(params):
+ root = etree.Element('refunds')
+ root.set('version', '3')
+
+ sender = params.pop('sender')
+ sender_xml = etree.SubElement(root, 'sender')
+ for name, value in sender.items():
+ etree.SubElement(sender_xml, name).text = __serialize(value)
+
+ refunds = params.pop('refunds')
+ for refund in refunds:
+ refund_xml = etree.SubElement(root, 'refund')
+ for name, value in refund.items():
+ etree.SubElement(refund_xml, name).text = __serialize(value)
+
+ for name, value in params.items():
+ etree.SubElement(root, name).text = __serialize(value)
+
+ return etree.tostring(root)
+
+
def __compact_notification_addresses(addresses_):
"""
Input::
diff --git a/tests/test_sofort.py b/tests/test_sofort.py
index 82d559b..2cc5a46 100644
--- a/tests/test_sofort.py
+++ b/tests/test_sofort.py
@@ -63,6 +63,23 @@ def test_pay(self):
self.assertIsInstance(tran.payment_url, basestring)
self.assertEqual('https://www.sofort.com/payment/go/136b2012718da0160fac20c2ec2f51100c90406e', tran.payment_url)
+ def test_refunds(self):
+ self.client._request_xml = MagicMock(return_value=REFUNDS_RESPONSE)
+ refunds_response = self.client.refunds(sender={
+ 'holder': 'Max Samplemerchant',
+ 'iban': 'DE71700111109999999999',
+ 'bic': 'DEKTDE7GXXX'
+ }, refunds=[{
+ 'transaction': '00000-00000-00000000-0000',
+ 'amount': '1.11',
+ 'comment': 'Order cancelled by user.',
+ 'reason_1': 'OrderID 123456',
+ 'reason_2': 'Refund'
+ }])
+ self.assertEqual('Max Samplemerchant', refunds_response.sender.holder)
+ self.assertEqual('00000-00000-00000000-0000', refunds_response.refund.transaction)
+ self.assertEqual('accepted', refunds_response.refund.status)
+
def test_details_multiple_transaction_ids(self):
self.client._request_xml = MagicMock(return_value=TRANSACTION_LIST_BY_IDS_RESPONSE)
tran_id = [
@@ -479,6 +496,35 @@ def test_empty_response(self):
"""
+REFUNDS_RESPONSE = u"""
+
+
+ Max Samplemerchant
+ Demo Bank
+ DE71700111109999999999
+ DEKTDE7GXXX
+
+ Test Refund December 5, 2013
+ [Base-64 encoded content for PAIN-file]
+
+
+ Max Mustermann
+ Demo Bank
+ DE06000000000023456789
+ SFRTDE20XXX
+
+ 00000-00000-00000000-0000
+ 1.11
+ Order cancelled by user.
+ OrderID 123456
+ Refund
+
+ fb1244caad
+ accepted
+
+
+"""
+
NEST_ERRORS = """
diff --git a/tests/test_xml.py b/tests/test_xml.py
index 586a806..44b1a8b 100644
--- a/tests/test_xml.py
+++ b/tests/test_xml.py
@@ -62,6 +62,28 @@ def test_transactions_request_by_params(self):
sofort.xml.transaction_request_by_params(params)
)
+ def test_refunds_by_params(self):
+ from collections import OrderedDict
+ params = {
+ 'sender': {
+ 'holder': 'Max Samplemerchant',
+ 'iban': 'DE71700111109999999999',
+ 'bic': 'DEKTDE7GXXX'
+ },
+ 'refunds': [{
+ 'transaction': '00000-00000-00000000-0000',
+ 'amount': '1.11',
+ 'comment': 'Order cancelled by user.',
+ 'reason_1': 'OrderID 123456',
+ 'reason_2': 'Refund'
+ }]
+ }
+
+ self.assertXmlEqual(
+ REFUNDS_SAMPLE,
+ sofort.xml.refunds_by_params(params)
+ )
+
MULTIPAY_SAMPLE = """
@@ -96,3 +118,20 @@ def test_transactions_request_by_params(self):
10
"""
+
+REFUNDS_SAMPLE = """
+
+
+ DE71700111109999999999
+ Max Samplemerchant
+ DEKTDE7GXXX
+
+
+ Order cancelled by user.
+ OrderID 123456
+ 1.11
+ 00000-00000-00000000-0000
+ Refund
+
+
+"""