Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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