Przeglądaj źródła

Got end-to-end Datacash transactions working

master
David Winterbottom 14 lat temu
rodzic
commit
3641d7ede8

+ 0
- 72
examples/demo/import.py Wyświetl plik

@@ -1,72 +0,0 @@
1
-import MySQLdb
2
-from decimal import Decimal as D
3
-
4
-from django.core.management import setup_environ
5
-import settings as app_settings
6
-setup_environ(app_settings)
7
-
8
-from oscar.product.models import ItemClass, Item, AttributeType, ItemAttributeValue, Option
9
-from oscar.stock.models import Partner, StockRecord
10
-from django.db import connection as django_connection
11
-
12
-connection = MySQLdb.connect(host='localhost', user='root', passwd='gsiwmm', db='CWDM_app_production_copy')
13
-cursor = connection.cursor(MySQLdb.cursors.DictCursor)
14
-
15
-
16
-def create_stock_record(product, partner_type, product_id, partner_ref):
17
-    cursor.execute("SELECT * FROM Brd_ProductStock WHERE ProductId = %s", product_id)
18
-    stock_row = cursor.fetchone()
19
-    if stock_row:
20
-        partner,_ = Partner.objects.get_or_create(name="%s (%s)" % (stock_row['Partner'], partner_type))
21
-        price = D(stock_row['Price']) - D(stock_row['PriceSalesTax'])
22
-        print "  - Creating stock record"
23
-        stock_record = StockRecord.objects.create(product=product, partner=partner, partner_reference=partner_ref,
24
-                                                  price_currency='GBP', price_excl_tax=price, 
25
-                                                  num_in_stock=stock_row['NumInStock'])
26
-
27
-# Truncate tables
28
-print "Truncating tables"
29
-django_cursor = django_connection.cursor()
30
-django_cursor.execute("set foreign_key_checks=0")
31
-django_cursor.execute("TRUNCATE TABLE product_item")
32
-django_cursor.execute("TRUNCATE TABLE product_attributetype")
33
-django_cursor.execute("TRUNCATE TABLE product_attributevalueoption")
34
-django_cursor.execute("TRUNCATE TABLE product_itemattributevalue")
35
-django_cursor.execute("TRUNCATE TABLE stock_partner")
36
-django_cursor.execute("TRUNCATE TABLE stock_stockrecord")
37
-
38
-cursor.execute("SELECT * FROM Brd_ProductsCanonical WHERE Partner = 'Prolog' LIMIT 20")
39
-for row in cursor.fetchall():
40
-    print "Creating canonical product %s" % (row['ProductCode'],)
41
-    pclass,_ = ItemClass.objects.get_or_create(name=row['MediaType'])
42
-    p,_ = Item.objects.get_or_create(title=row['Title'], item_class=pclass, upc=row['ProductCode'])
43
-    
44
-    cursor.execute("SELECT * FROM Brd_Products WHERE CanonicalProductId = %s", row['CanonicalProductId'])
45
-    product_rows = cursor.fetchall()
46
-    if len(product_rows) > 1:
47
-        # Create variant products
48
-        for product_row in product_rows:
49
-            print " - Creating child product %s" % product_row['SourceProductId']
50
-            child_p,_ = Item.objects.get_or_create(parent=p, upc=product_row['SourceProductId'])
51
-            
52
-            # Fetch attributes
53
-            cursor.execute("SELECT VariantType, VariantValue FROM Brd_ProductVariations WHERE ProductId = %s", product_row['ProductId'])
54
-            variant_rows = cursor.fetchall()
55
-            for variant_row in variant_rows:
56
-                print "   - Create attribute %s = %s" % (variant_row['VariantType'], variant_row['VariantValue'])
57
-                attr_type,_ = AttributeType.objects.get_or_create(name=variant_row['VariantType'])
58
-                item_attr_value = ItemAttributeValue.objects.create(product=child_p, type=attr_type, 
59
-                                                                    value=variant_row['VariantValue'])
60
-                if variant_row['VariantType'] == 'CanBePersonalised' and variant_row['VariantValue'] == 'Y':
61
-                    option,_ = Option.objects.get_or_create(name="Personal message")
62
-                    child_p.options.add(option)
63
-                    child_p.save()
64
-            # Stock record for child
65
-            create_stock_record(child_p, row['FulfillmentType'], product_row['ProductId'], product_row['SourceProductId'])
66
-    else:
67
-        # Stand-alone product
68
-        product_row = product_rows[0]
69
-        create_stock_record(p, row['FulfillmentType'], product_row['ProductId'], product_row['SourceProductId'])
70
-    
71
-cursor.close()
72
-print "Finished"

+ 1
- 0
examples/demo/shop/checkout/views.py Wyświetl plik

@@ -12,6 +12,7 @@ import_module('payment.utils', ['Bankcard'], locals())
12 12
 import_module('payment.datacash.utils', ['Gateway', 'Facade'], locals())
13 13
 import_module('order.models', ['PaymentEvent', 'PaymentEventType', 'PaymentEventQuantity'], locals())
14 14
     
15
+    
15 16
 class PaymentMethodView(CorePaymentMethodView):
16 17
     template_file = 'checkout/payment_method.html'
17 18
     

+ 7
- 2
oscar/apps/payment/datacash/abstract_models.py Wyświetl plik

@@ -1,7 +1,6 @@
1
-from decimal import Decimal
1
+import re
2 2
 
3 3
 from django.db import models
4
-from django.utils.translation import ugettext as _
5 4
 
6 5
 
7 6
 class AbstractOrderTransaction(models.Model):
@@ -29,3 +28,9 @@ class AbstractOrderTransaction(models.Model):
29 28
     
30 29
     class Meta:
31 30
         abstract = True
31
+        
32
+    def save(self, *args, **kwargs):
33
+        if not self.pk:
34
+            reg_ex = re.compile(r'\d{12}')
35
+            self.request_xml = reg_ex.sub('XXXXXXXXXXXX', self.request_xml)
36
+        super(AbstractOrderTransaction, self).save(*args, **kwargs)

+ 91
- 0
oscar/apps/payment/datacash/tests.py Wyświetl plik

@@ -0,0 +1,91 @@
1
+from decimal import Decimal as D
2
+from xml.dom.minidom import parseString
3
+import datetime
4
+
5
+from django.test import TestCase
6
+from django.conf import settings
7
+
8
+from oscar.apps.payment.datacash.models import OrderTransaction
9
+from oscar.apps.payment.datacash.utils import Gateway, Facade
10
+from oscar.apps.payment.utils import Bankcard
11
+
12
+
13
+class OrderTransactionTests(TestCase):
14
+    
15
+    def test_cc_numbers_are_not_saved(self):
16
+        
17
+        request_xml = """<?xml version="1.0" encoding="UTF-8" ?>
18
+<Request>
19
+    <Authentication>
20
+        <client>99000001</client>
21
+        <password>boomboom</password>
22
+    </Authentication>
23
+    <Transaction>
24
+    <CardTxn>
25
+        <Card>
26
+            <pan>1000011100000004</pan>
27
+            <expirydate>04/06</expirydate>
28
+            <startdate>01/04</startdate>
29
+        </Card>
30
+        <method>auth</method>
31
+    </CardTxn>
32
+    <TxnDetails>
33
+        <merchantreference>1000001</merchantreference>
34
+        <amount currency="GBP">95.99</amount>
35
+    </TxnDetails>
36
+    </Transaction>
37
+</Request>"""
38
+
39
+        response_xml = """<?xml version="1.0" encoding="UTF-8" ?>
40
+<Response>
41
+    <CardTxn>
42
+        <authcode>060642</authcode>
43
+        <card_scheme>Switch</card_scheme>
44
+        <country>United Kingdom</country>
45
+        <issuer>HSBC</issuer>
46
+    </CardTxn>
47
+    <datacash_reference>3000000088888888</datacash_reference>
48
+    <merchantreference>1000001</merchantreference>
49
+    <mode>LIVE</mode>
50
+    <reason>ACCEPTED</reason>
51
+    <status>1</status>
52
+    <time>1071567305</time>
53
+</Response>"""
54
+        
55
+        txn = OrderTransaction.objects.create(order_number='1000',
56
+                                              method='auth',
57
+                                              datacash_ref='3000000088888888',
58
+                                              merchant_ref='1000001',
59
+                                              amount=D('95.99'),
60
+                                              status=1,
61
+                                              reason='ACCEPTED',
62
+                                              request_xml=request_xml,
63
+                                              response_xml=response_xml)
64
+        doc = parseString(txn.request_xml)
65
+        element = doc.getElementsByTagName('pan')[0]
66
+        self.assertEqual('XXXXXXXXXXXX0004', element.firstChild.data)
67
+        
68
+        
69
+class IntegrationTests(TestCase):
70
+    
71
+    def _test_for_smoke(self):
72
+        gateway = Gateway(settings.DATACASH_CLIENT, 
73
+                          settings.DATACASH_PASSWORD,
74
+                          host=settings.DATACASH_HOST)
75
+        response = gateway.auth(card_number='1000011000000005',
76
+                                expiry_date='01/13',
77
+                                amount=D('50.00'),
78
+                                currency='GBP',
79
+                                merchant_reference='123456_%s' % datetime.datetime.now().microsecond)
80
+        print response
81
+        
82
+    def _test_adapter(self):
83
+        bankcard = Bankcard(card_number='1000011000000005', expiry_date='01/13')
84
+    
85
+        dc_facade = Facade()
86
+        reference = dc_facade.debit('102910', D('23.00'), bankcard)
87
+        print reference
88
+        
89
+        OrderTransaction.objects.get(order_number='102910')
90
+        
91
+    

+ 21
- 23
oscar/apps/payment/datacash/utils.py Wyświetl plik

@@ -1,5 +1,6 @@
1 1
 import datetime
2 2
 from xml.dom.minidom import Document, parseString
3
+import httplib, urllib
3 4
 
4 5
 from django.conf import settings
5 6
 from django.db import transaction
@@ -7,40 +8,34 @@ from django.utils.translation import ugettext_lazy as _
7 8
 
8 9
 from oscar.core.loading import import_module
9 10
 import_module('payment.datacash.models', ['OrderTransaction'], locals())
10
-import_module('payment.exceptions', ['TransactionDeclinedException'], locals())
11
+import_module('payment.exceptions', ['TransactionDeclinedException', 'GatewayException'], locals())
11 12
 
12 13
 # Status codes
13 14
 ACCEPTED, DECLINED = '1', '7'
14 15
 
16
+
15 17
 class Gateway(object):
16 18
 
17
-    def __init__(self, client, password, cv2avs=False):
19
+    def __init__(self, client, password, host, cv2avs=False):
18 20
         self._client = client
19 21
         self._password = password
22
+        self._host = host
20 23
         
21 24
         # Fraud settings
22 25
         self._cv2avs = cv2avs
23 26
 
24 27
     def do_request(self, request_xml):
25 28
         # Need to fill in HTTP request here
26
-        response_xml = """<?xml version="1.0" encoding="UTF-8" ?>
27
-<Response>
28
-<CardTxn>
29
-<authcode>060642</authcode>
30
-<card_scheme>Switch</card_scheme>
31
-<country>United Kingdom</country>
32
-<issuer>HSBC</issuer>
33
-</CardTxn>
34
-<datacash_reference>3000000088888888</datacash_reference>
35
-<merchantreference>1000001</merchantreference>
36
-<mode>LIVE</mode>
37
-<reason>ACCEPTED</reason>
38
-<status>1</status>
39
-<time>1071567305</time>
40
-</Response>
41
-
42
-"""
43
-
29
+        conn = httplib.HTTPSConnection(self._host, 443, timeout=30)
30
+        headers = {"Content-type": "application/xml",
31
+                   "Accept": ""}
32
+        conn.request("POST", "/Transaction", request_xml, headers)
33
+        response = conn.getresponse()
34
+        response_xml = response.read()
35
+        if response.status != httplib.OK:
36
+            raise GatewayException("Unable to communicate with payment gateway (code: %s, response: %s)" % (response.status, response_xml))
37
+        conn.close()
38
+        
44 39
         # Save response XML
45 40
         self._last_response_xml = response_xml
46 41
         return response_xml
@@ -120,12 +115,15 @@ class Gateway(object):
120 115
         return ele
121 116
     
122 117
     def _get_element_text(self, doc, tag):
123
-        ele = doc.getElementsByTagName(tag)[0]
118
+        try:
119
+            ele = doc.getElementsByTagName(tag)[0]
120
+        except IndexError:
121
+            return None
124 122
         return ele.firstChild.data
125 123
 
126 124
     def _build_response_dict(self, response_xml, extra_elements=None):
127 125
         doc = parseString(response_xml)
128
-        response = {'status': self._get_element_text(doc, 'status'),
126
+        response = {'status': int(self._get_element_text(doc, 'status')),
129 127
                     'datacash_reference': self._get_element_text(doc, 'datacash_reference'),
130 128
                     'merchant_reference': self._get_element_text(doc, 'merchantreference'),
131 129
                     'reason': self._get_element_text(doc, 'reason')}
@@ -200,7 +198,7 @@ class Facade(object):
200 198
     """
201 199
     
202 200
     def __init__(self):
203
-        self.gateway = Gateway(settings.DATACASH_CLIENT, settings.DATACASH_PASSWORD)
201
+        self.gateway = Gateway(settings.DATACASH_CLIENT, settings.DATACASH_PASSWORD, settings.DATACASH_HOST)
204 202
     
205 203
     def debit(self, order_number, amount, bankcard, billing_address=None):
206 204
         with transaction.commit_on_success():

+ 4
- 0
oscar/apps/payment/exceptions.py Wyświetl plik

@@ -1,2 +1,6 @@
1 1
 class TransactionDeclinedException(Exception):
2
+    pass
3
+
4
+
5
+class GatewayException(Exception):
2 6
     pass

+ 1
- 5
oscar/apps/payment/tests/datacash_requests.py Wyświetl plik

@@ -54,7 +54,7 @@ class TransactionMixin(object):
54 54
             tag = doc.getElementsByTagName(tag_name)[0]
55 55
             self.assertEquals(value, tag.attributes[attribute].value)
56 56
         except IndexError:
57
-            self.fail("Tag '%s' not found\n%s" % (tagName, request_xml))
57
+            self.fail("Tag '%s' not found\n%s" % (tag_name, request_xml))
58 58
     
59 59
     def test_request_includes_credentials(self):
60 60
         self.make_request()
@@ -87,10 +87,6 @@ class InitialTransactionMixin(TransactionMixin):
87 87
     def test_request_can_include_issue_number(self):
88 88
         self.make_request(issue_number='03')    
89 89
         self.assertXmlTagValue('issuenumber', '03')    
90
-        
91
-    def test_request_can_include_authcode(self):
92
-        self.make_request(auth_code='334455')    
93
-        self.assertXmlTagValue('authcode', '334455')
94 90
 
95 91
 
96 92
 class AuthTransactionTests(TestCase, InitialTransactionMixin):

+ 0
- 13
oscar/apps/payment/tests/datacash_responses.py Wyświetl plik

@@ -55,19 +55,6 @@ class AuthResponseHandlingTests(TestCase):
55 55
         self.assertEquals('060642', response['auth_code'])
56 56
         self.assertEquals('ACCEPTED', response['reason'])
57 57
         
58
-    def test_success_pre_response(self):
59
-        self.gateway.do_request.return_value = self.success_response_xml
60
-        response = self.gateway.refund(card_number='1000010000000007', 
61
-                                     expiry_date='01/12',
62
-                                     merchant_reference='1000001',
63
-                                     currency='GBP',
64
-                                     amount=D('12.99'))
65
-        self.assertEquals('1', response['status'])
66
-        self.assertEquals('3000000088888888', response['datacash_reference'])
67
-        self.assertEquals('1000001', response['merchant_reference'])
68
-        self.assertEquals('060642', response['auth_code'])
69
-        self.assertEquals('ACCEPTED', response['reason'])
70
-        
71 58
     def test_declined_auth_response(self):
72 59
         response_xml = """<?xml version="1.0" encoding="UTF-8" ?>
73 60
 <Response>

+ 3
- 2
oscar/apps/payment/utils.py Wyświetl plik

@@ -2,9 +2,10 @@
2 2
 
3 3
 class Bankcard(object):
4 4
     
5
-    def __init__(self, card_number, expiry_date, name=None, start_date=None, issue_number=None):
5
+    def __init__(self, card_number, expiry_date, name=None, ccv=None, start_date=None, issue_number=None):
6 6
         self.card_number = card_number
7 7
         self.card_holder_name = name
8 8
         self.expiry_date = expiry_date
9 9
         self.start_date = start_date
10
-        self.issue_number = issue_number
10
+        self.issue_number = issue_number
11
+        self.ccv = ccv

+ 1
- 1
settings_local.py Wyświetl plik

@@ -15,6 +15,6 @@ GOOGLE_CHECKOUT_MERCHANT_KEY = 'ba3VMi0dFHqKKSDEfoAuLA'
15 15
 GOOGLE_CHECKOUT_URL = 'https://sandbox.google.com/checkout/api/checkout/v2/merchantCheckout/Merchant/%s' % GOOGLE_CHECKOUT_MERCHANT_ID
16 16
 
17 17
 # Datacash
18
-DATACASH_URL = 'https://testserver.datacash.com/Transaction'         
18
+DATACASH_HOST = 'testserver.datacash.com'         
19 19
 DATACASH_CLIENT = '99002051'
20 20
 DATACASH_PASSWORD = 'Ys4ePPgDHQmn'

Ładowanie…
Anuluj
Zapisz