|
|
@@ -1,245 +0,0 @@
|
|
1
|
|
-import datetime
|
|
2
|
|
-from xml.dom.minidom import Document, parseString
|
|
3
|
|
-import httplib
|
|
4
|
|
-import urllib
|
|
5
|
|
-
|
|
6
|
|
-from django.conf import settings
|
|
7
|
|
-from django.db import transaction
|
|
8
|
|
-from django.utils.translation import ugettext_lazy as _
|
|
9
|
|
-from django.core.mail import mail_admins
|
|
10
|
|
-
|
|
11
|
|
-from oscar.core.loading import import_module
|
|
12
|
|
-import_module('payment.datacash.models', ['OrderTransaction'], locals())
|
|
13
|
|
-import_module('payment.exceptions', ['TransactionDeclined', 'GatewayError',
|
|
14
|
|
- 'InvalidGatewayRequestError'], locals())
|
|
15
|
|
-
|
|
16
|
|
-# Status codes
|
|
17
|
|
-ACCEPTED, DECLINED, INVALID_CREDENTIALS = '1', '7', '10'
|
|
18
|
|
-
|
|
19
|
|
-
|
|
20
|
|
-class Gateway(object):
|
|
21
|
|
-
|
|
22
|
|
- def __init__(self, client, password, host, cv2avs=False):
|
|
23
|
|
- self._client = client
|
|
24
|
|
- self._password = password
|
|
25
|
|
- self._host = host
|
|
26
|
|
-
|
|
27
|
|
- # Fraud settings
|
|
28
|
|
- self._cv2avs = cv2avs
|
|
29
|
|
-
|
|
30
|
|
- def do_request(self, request_xml):
|
|
31
|
|
- # Need to fill in HTTP request here
|
|
32
|
|
- conn = httplib.HTTPSConnection(self._host, 443, timeout=30)
|
|
33
|
|
- headers = {"Content-type": "application/xml",
|
|
34
|
|
- "Accept": ""}
|
|
35
|
|
- conn.request("POST", "/Transaction", request_xml, headers)
|
|
36
|
|
- response = conn.getresponse()
|
|
37
|
|
- response_xml = response.read()
|
|
38
|
|
- if response.status != httplib.OK:
|
|
39
|
|
- raise GatewayError("Unable to communicate with payment gateway (code: %s, response: %s)" % (response.status, response_xml))
|
|
40
|
|
- conn.close()
|
|
41
|
|
-
|
|
42
|
|
- # Save response XML
|
|
43
|
|
- self._last_response_xml = response_xml
|
|
44
|
|
- return response_xml
|
|
45
|
|
-
|
|
46
|
|
- def _build_request_xml(self, method_name, **kwargs):
|
|
47
|
|
- """
|
|
48
|
|
- Builds the XML for a 'initial' transaction
|
|
49
|
|
- """
|
|
50
|
|
- doc = Document()
|
|
51
|
|
- req = self._create_element(doc, doc, 'Request')
|
|
52
|
|
-
|
|
53
|
|
- # Authentication
|
|
54
|
|
- auth = self._create_element(doc, req, 'Authentication')
|
|
55
|
|
- self._create_element(doc, auth, 'client', self._client)
|
|
56
|
|
- self._create_element(doc, auth, 'password', self._password)
|
|
57
|
|
-
|
|
58
|
|
- # Transaction
|
|
59
|
|
- txn = self._create_element(doc, req, 'Transaction')
|
|
60
|
|
-
|
|
61
|
|
- # CardTxn
|
|
62
|
|
- if 'card_number' in kwargs:
|
|
63
|
|
- card_txn = self._create_element(doc, txn, 'CardTxn')
|
|
64
|
|
- self._create_element(doc, card_txn, 'method', method_name)
|
|
65
|
|
-
|
|
66
|
|
- card = self._create_element(doc, card_txn, 'Card')
|
|
67
|
|
- self._create_element(doc, card, 'pan', kwargs['card_number'])
|
|
68
|
|
- self._create_element(doc, card, 'expirydate', kwargs['expiry_date'])
|
|
69
|
|
-
|
|
70
|
|
- if 'start_date' in kwargs:
|
|
71
|
|
- self._create_element(doc, card, 'startdate', kwargs['start_date'])
|
|
72
|
|
-
|
|
73
|
|
- if 'issue_number' in kwargs:
|
|
74
|
|
- self._create_element(doc, card, 'issuenumber', kwargs['issue_number'])
|
|
75
|
|
-
|
|
76
|
|
- if 'auth_code' in kwargs:
|
|
77
|
|
- self._create_element(doc, card, 'authcode', kwargs['auth_code'])
|
|
78
|
|
-
|
|
79
|
|
- if self._cv2avs:
|
|
80
|
|
- self._add_cv2avs_elements(doc, card, kwargs)
|
|
81
|
|
-
|
|
82
|
|
- # HistoricTxn
|
|
83
|
|
- if 'txn_reference' in kwargs:
|
|
84
|
|
- historic_txn = self._create_element(doc, txn, 'HistoricTxn')
|
|
85
|
|
- self._create_element(doc, historic_txn, 'reference', kwargs['txn_reference'])
|
|
86
|
|
- self._create_element(doc, historic_txn, 'method', method_name)
|
|
87
|
|
- if 'auth_code' in kwargs:
|
|
88
|
|
- self._create_element(doc, historic_txn, 'authcode', kwargs['auth_code'])
|
|
89
|
|
-
|
|
90
|
|
- # TxnDetails
|
|
91
|
|
- if 'amount' in kwargs:
|
|
92
|
|
- txn_details = self._create_element(doc, txn, 'TxnDetails')
|
|
93
|
|
- if 'merchant_reference' in kwargs:
|
|
94
|
|
- self._create_element(doc, txn_details, 'merchantreference', kwargs['merchant_reference'])
|
|
95
|
|
- self._create_element(doc, txn_details, 'amount', str(kwargs['amount']), {'currency': kwargs['currency']})
|
|
96
|
|
-
|
|
97
|
|
- # Save XML for later retrieval
|
|
98
|
|
- self._last_request_xml = doc.toxml()
|
|
99
|
|
-
|
|
100
|
|
- return self.do_request(doc.toxml())
|
|
101
|
|
-
|
|
102
|
|
- def _add_cv2avs_elements(self, doc, card, kwargs):
|
|
103
|
|
- cv2avs = self._create_element(doc, card, 'Cv2Avs')
|
|
104
|
|
- if 'ccv' in kwargs:
|
|
105
|
|
- self._create_element(doc, cv2avs, 'cv2', kwargs['ccv'])
|
|
106
|
|
-
|
|
107
|
|
- def _create_element(self, doc, parent, tag, value=None, attributes=None):
|
|
108
|
|
- """
|
|
109
|
|
- Creates an XML element
|
|
110
|
|
- """
|
|
111
|
|
- ele = doc.createElement(tag)
|
|
112
|
|
- parent.appendChild(ele)
|
|
113
|
|
- if value:
|
|
114
|
|
- text = doc.createTextNode(value)
|
|
115
|
|
- ele.appendChild(text)
|
|
116
|
|
- if attributes:
|
|
117
|
|
- [ele.setAttribute(k, v) for k,v in attributes.items()]
|
|
118
|
|
- return ele
|
|
119
|
|
-
|
|
120
|
|
- def _get_element_text(self, doc, tag):
|
|
121
|
|
- try:
|
|
122
|
|
- ele = doc.getElementsByTagName(tag)[0]
|
|
123
|
|
- except IndexError:
|
|
124
|
|
- return None
|
|
125
|
|
- return ele.firstChild.data
|
|
126
|
|
-
|
|
127
|
|
- def _build_response_dict(self, response_xml, extra_elements=None):
|
|
128
|
|
- doc = parseString(response_xml)
|
|
129
|
|
- response = {'status': int(self._get_element_text(doc, 'status')),
|
|
130
|
|
- 'datacash_reference': self._get_element_text(doc, 'datacash_reference'),
|
|
131
|
|
- 'merchant_reference': self._get_element_text(doc, 'merchantreference'),
|
|
132
|
|
- 'reason': self._get_element_text(doc, 'reason')}
|
|
133
|
|
- if extra_elements:
|
|
134
|
|
- for tag, key in extra_elements.items():
|
|
135
|
|
- response[key] = self._get_element_text(doc, tag)
|
|
136
|
|
- return response
|
|
137
|
|
-
|
|
138
|
|
- # API
|
|
139
|
|
-
|
|
140
|
|
- def auth(self, **kwargs):
|
|
141
|
|
- """
|
|
142
|
|
- Performs an 'auth' request, which is to debit the money immediately
|
|
143
|
|
- as a one-off transaction.
|
|
144
|
|
-
|
|
145
|
|
- Note that currency should be ISO 4217 Alphabetic format.
|
|
146
|
|
- """
|
|
147
|
|
- self._check_kwargs(kwargs, ['amount', 'currency', 'card_number', 'expiry_date', 'merchant_reference'])
|
|
148
|
|
- response_xml = self._build_request_xml('auth', **kwargs)
|
|
149
|
|
- return self._build_response_dict(response_xml, {'authcode': 'auth_code'})
|
|
150
|
|
-
|
|
151
|
|
- def pre(self, **kwargs):
|
|
152
|
|
- """
|
|
153
|
|
- Performs an 'pre' request, which is to ring-fence the requested money
|
|
154
|
|
- so it can be fulfilled at a later time.
|
|
155
|
|
- """
|
|
156
|
|
- self._check_kwargs(kwargs, ['amount', 'currency', 'card_number', 'expiry_date', 'merchant_reference'])
|
|
157
|
|
- response_xml = self._build_request_xml('pre', **kwargs)
|
|
158
|
|
- return self._build_response_dict(response_xml, {'authcode': 'auth_code'})
|
|
159
|
|
-
|
|
160
|
|
- def refund(self, **kwargs):
|
|
161
|
|
- self._check_kwargs(kwargs, ['amount', 'currency', 'card_number', 'expiry_date', 'merchant_reference'])
|
|
162
|
|
- response_xml = self._build_request_xml('refund', **kwargs)
|
|
163
|
|
- return self._build_response_dict(response_xml, {'authcode': 'auth_code'})
|
|
164
|
|
-
|
|
165
|
|
- def erp(self, **kwargs):
|
|
166
|
|
- self._check_kwargs(kwargs, ['amount', 'currency', 'card_number', 'expiry_date', 'merchant_reference'])
|
|
167
|
|
- response_xml = self._build_request_xml('erp', **kwargs)
|
|
168
|
|
- return self._build_response_dict(response_xml, {'authcode': 'auth_code'})
|
|
169
|
|
-
|
|
170
|
|
- # "Historic" transaction types
|
|
171
|
|
-
|
|
172
|
|
- def cancel(self, txn_reference):
|
|
173
|
|
- response_xml = self._build_request_xml('cancel', txn_reference=txn_reference)
|
|
174
|
|
- return self._build_response_dict(response_xml)
|
|
175
|
|
-
|
|
176
|
|
- def fulfil(self, **kwargs):
|
|
177
|
|
- self._check_kwargs(kwargs, ['amount', 'currency', 'txn_reference', 'auth_code'])
|
|
178
|
|
- response_xml = self._build_request_xml('fulfil', **kwargs)
|
|
179
|
|
- return self._build_response_dict(response_xml)
|
|
180
|
|
-
|
|
181
|
|
- def txn_refund(self, **kwargs):
|
|
182
|
|
- self._check_kwargs(kwargs, ['amount', 'currency', 'txn_reference'])
|
|
183
|
|
- response_xml = self._build_request_xml('txn_refund', **kwargs)
|
|
184
|
|
- return self._build_response_dict(response_xml)
|
|
185
|
|
-
|
|
186
|
|
- def last_request_xml(self):
|
|
187
|
|
- return self._last_request_xml
|
|
188
|
|
-
|
|
189
|
|
- def last_response_xml(self):
|
|
190
|
|
- return self._last_response_xml
|
|
191
|
|
-
|
|
192
|
|
- def _check_kwargs(self, kwargs, required_keys):
|
|
193
|
|
- for key in required_keys:
|
|
194
|
|
- if key not in kwargs:
|
|
195
|
|
- raise RuntimeError('You must provide a "%s" argument' % key)
|
|
196
|
|
-
|
|
197
|
|
-
|
|
198
|
|
-class Facade(object):
|
|
199
|
|
- """
|
|
200
|
|
- Responsible for dealing with oscar objects
|
|
201
|
|
- """
|
|
202
|
|
-
|
|
203
|
|
- def __init__(self):
|
|
204
|
|
- self.gateway = Gateway(settings.DATACASH_CLIENT, settings.DATACASH_PASSWORD, settings.DATACASH_HOST)
|
|
205
|
|
-
|
|
206
|
|
- def debit(self, order_number, amount, bankcard, basket, billing_address=None):
|
|
207
|
|
- with transaction.commit_on_success():
|
|
208
|
|
- response = self.gateway.auth(card_number=bankcard.card_number,
|
|
209
|
|
- expiry_date=bankcard.expiry_date,
|
|
210
|
|
- amount=amount,
|
|
211
|
|
- currency='GBP',
|
|
212
|
|
- merchant_reference=self.generate_merchant_reference(order_number),
|
|
213
|
|
- ccv=bankcard.ccv)
|
|
214
|
|
-
|
|
215
|
|
- # Create transaction model irrespective of whether transaction was successful or not
|
|
216
|
|
- txn = OrderTransaction.objects.create(order_number=order_number,
|
|
217
|
|
- basket=basket,
|
|
218
|
|
- method='auth',
|
|
219
|
|
- datacash_ref=response['datacash_reference'],
|
|
220
|
|
- merchant_ref=response['merchant_reference'],
|
|
221
|
|
- amount=amount,
|
|
222
|
|
- auth_code=response['auth_code'],
|
|
223
|
|
- status=int(response['status']),
|
|
224
|
|
- reason=response['reason'],
|
|
225
|
|
- request_xml=self.gateway.last_request_xml(),
|
|
226
|
|
- response_xml=self.gateway.last_response_xml())
|
|
227
|
|
-
|
|
228
|
|
- # Test if response is successful
|
|
229
|
|
- if response['status'] == INVALID_CREDENTIALS:
|
|
230
|
|
- # This needs to notify the administrators straight away
|
|
231
|
|
- import pprint
|
|
232
|
|
- msg = "Order #%s:\n%s" % (order_number, pprint.pprint(response))
|
|
233
|
|
- mail_admins("Datacash credentials are not valid", msg)
|
|
234
|
|
- raise InvalidGatewayRequestError("Unable to communicate with payment gateway, please try again later")
|
|
235
|
|
-
|
|
236
|
|
- if response['status'] == DECLINED:
|
|
237
|
|
- raise TransactionDeclined("Your bank declined this transaction, please check your details and try again")
|
|
238
|
|
-
|
|
239
|
|
- return response['datacash_reference']
|
|
240
|
|
-
|
|
241
|
|
- def generate_merchant_reference(self, order_number):
|
|
242
|
|
- return '%s_%s' % (order_number, datetime.datetime.now().microsecond)
|
|
243
|
|
-
|
|
244
|
|
-
|
|
245
|
|
-
|