Commits

Bruce Kroeze committed a930629

Added protx gateway

Comments (0)

Files changed (8)

bursar/bursar_settings_customize.py

          'TRANKEY': "", #Your Cybersource transaction key - REQUIRED
          'EXTRA_LOGGING': False
      },
+     'PROTX': {
+         'LIVE': False,
+         'SIMULATOR': False, # Simulated transaction flag - must be false to accept real payments.
+         'SKIP_POST': False, # For testing only, this will skip actually posting to Prot/x servers.  
+                             # This is because their servers restrict IPs of posting servers, even for tests.
+                             # If you are developing on a desktop, you'll have to enable this.
+
+         'CAPTURE': "PAYMENT" # Should be "PAYMENT" or "DEFERRED", Note that you can only use the latter if
+                              # you set that option on your Prot/X account first.
+         'LABEL': _('Prot/X Secure Payments'),
+         'CREDITCHOICES': (
+                     (('VISA','Visa Credit/Debit')),
+                     #(('UKE','Visa Electron')),
+                     #(('DELTA','Delta')),
+                     #(('AMEX','American Express')),  # not always available
+                     #(('DC','Diners Club')), # not always available
+                     (('MC','Mastercard')),
+                     #(('MAESTRO','UK Maestro')),
+                     #(('SOLO','Solo')),
+                     #(('JCB','JCB')),
+                 ),
+
+         'VENDOR': "", # REQUIRED, your vendor name. This is used for Live and Test transactions.  
+                      # Make sure to add your server IP address to VSP, or it won't work.
+
+         'VENDOR_SIMULATOR': "", # Simulator Vendor Name
+                                # This is used for Live and Test transactions.  Make sure to activate
+                                # the VSP Simulator (you have to directly request it) and add your
+                                # server IP address to the VSP Simulator, or it won't work.")),
+
+         'CURRENCY_CODE': 'GBP',
+
+         'EXTRA_LOGGING': False,
+     },
+     'PROTX_TEST': {
+         'LIVE': False,
+         'SIMULATOR': False,
+         'SKIP_POST': False,
+         'CAPTURE': "PAYMENT"
+         'CREDITCHOICES': (
+                     (('VISA','Visa Credit/Debit')),
+                     (('MC','Mastercard')),
+                 ),
+         'VENDOR': "", # REQUIRED
+         'VENDOR_SIMULATOR': "",
+         'EXTRA_LOGGING': False,
+     }
 }

bursar/gateway/base.py

         return self.payment
         
     def record_failure(self, amount=NOTSET, details="", authorization=None):
-        log.info('Recording a payment failure: order #%i, code %s\nmessage=%s', self.purchase.orderno, self.reason_code, details)
+        log.info('Recording a payment failure: purchase #%s order #%s, code %s\nmessage=%s', 
+            self.purchase, self.purchase.orderno, self.reason_code, details)
         self.amount = amount
             
         failure = PaymentFailure.objects.create(purchase=self.purchase, 
             details=details, 
             transaction_id=self.transaction_id,
             amount = self.amount,
-            payment = self.key,
+            method = self.key,
             reason_code = self.reason_code
         )
         return failure

bursar/gateway/cybersource_gateway/tests.py

 from django.test.client import Client
 
 SKIP_TESTS = False
-NEED_SETTINGS = """Tests for authorizenet_gateway module require a
+NEED_SETTINGS = """Tests for cybersource_gateway module require a
 CYBERSOURCE_TEST section in settings.BURSAR_SETTINGS.  At a 
-minimum, you must specify the MERCHANT_ID, TRANKEY, and STORE_NAME."""
+minimum, you must specify the 'MERCHANT_ID' and 'TRANKEY'."""
 
 class TestGateway(TestCase):
     def setUp(self):

bursar/gateway/protx_gateway/processor.py

 """Prot/X Payment Gateway.
-
-To use this module, enable it in your shop configuration, usually at http:yourshop/settings/
-
-To override the connection urls specified below in `PROTX_DEFAULT_URLS, add a dictionary in 
-your settings.py file called "PROTX_URLS", mapping the keys below to the urls you need for 
-your store.  You only need to override the specific urls that have changed, the processor
-will fall back to the defaults for any not specified in your dictionary.
 """
-from django.conf import settings
+from bursar.gateway.base import BasePaymentProcessor, ProcessorResult, NOTSET
+from bursar.errors import GatewayError
+from bursar.numbers import trunc_decimal
+from decimal import Decimal
+from django.utils.http import urlencode
 from django.utils.translation import ugettext_lazy as _
-from bursar.gateway.base import BasePaymentProcessor, ProcessorResult, NOTSET
-from bursar.numbers import trunc_decimal
-from django.utils.http import urlencode
-import forms
 import urllib2
 
 PROTOCOL = "2.22"
 
-PROTX_DEFAULT_URLS = {
-    'LIVE_CONNECTION' : 'https://ukvps.protx.com/vspgateway/service/vspdirect-register.vsp',
-    'LIVE_CALLBACK' : 'https://ukvps.protx.com/vspgateway/service/direct3dcallback.vsp',
-    'TEST_CONNECTION' : 'https://ukvpstest.protx.com/vspgateway/service/vspdirect-register.vsp',
-    'TEST_CALLBACK' : 'https://ukvpstest.protx.com/vspgateway/service/direct3dcallback.vsp',
-    'SIMULATOR_CONNECTION' : 'https://ukvpstest.protx.com/VSPSimulator/VSPDirectGateway.asp',
-    'SIMULATOR_CALLBACK' : 'https://ukvpstest.protx.com/VSPSimulator/VSPDirectCallback.asp'
-}
-
-FORM = forms.ProtxPayShipForm
-
 class PaymentProcessor(BasePaymentProcessor):
     packet = {}
     response = {}
     
-    def __init__(self, settings):
-        super(PaymentProcessor, self).__init__('Protx', settings)
+    def __init__(self, settings={}):
+        
+        working_settings = {
+            'LIVE_CONNECTION' : 'https://ukvps.protx.com/vspgateway/service/vspdirect-register.vsp',
+            'LIVE_CALLBACK' : 'https://ukvps.protx.com/vspgateway/service/direct3dcallback.vsp',
+            'TEST_CONNECTION' : 'https://ukvpstest.protx.com/vspgateway/service/vspdirect-register.vsp',
+            'TEST_CALLBACK' : 'https://ukvpstest.protx.com/vspgateway/service/direct3dcallback.vsp',
+            'SIMULATOR_CONNECTION' : 'https://ukvpstest.protx.com/VSPSimulator/VSPDirectGateway.asp',
+            'SIMULATOR_CALLBACK' : 'https://ukvpstest.protx.com/VSPSimulator/VSPDirectCallback.asp',
+            
+            'LIVE': False,
+            'SIMULATOR': False, # Simulated transaction flag - must be false to accept real payments.
+            'SKIP_POST': False, # For testing only, this will skip actually posting to Prot/x servers.  
+                                # This is because their servers restrict IPs of posting servers, even for tests.
+                                # If you are developing on a desktop, you'll have to enable this.
 
-        vendor = settings.VENDOR.value
-        if vendor == "":
-            self.log.warn('Prot/X Vendor is not set, please configure in your site configuration.')
-        if settings.SIMULATOR.value:
-            vendor = settings.VENDOR_SIMULATOR.value
-            if not vendor:
-                self.log.warn("You are trying to use the Prot/X VSP Simulator, but you don't have a vendor name in settings for the simulator.  I'm going to use the live vendor name, but that probably won't work.")
-                vendor = settings.VENDOR.value
+            'CAPTURE': "PAYMENT", # Should be "PAYMENT" or "DEFERRED", Note that you can only use the latter if
+                                  # you set that option on your Prot/X account first.
+            'LABEL': _('Prot/X Secure Payments'),
+            'CREDITCHOICES': (
+                        (('VISA','Visa Credit/Debit')),
+                        #(('UKE','Visa Electron')),
+                        #(('DELTA','Delta')),
+                        #(('AMEX','American Express')),  # not always available
+                        #(('DC','Diners Club')), # not always available
+                        (('MC','Mastercard')),
+                        #(('MAESTRO','UK Maestro')),
+                        #(('SOLO','Solo')),
+                        #(('JCB','JCB')),
+                    ),
+
+            'VENDOR': "", # REQUIRED, your vendor name. This is used for Live and Test transactions.  
+                         # Make sure to add your server IP address to VSP, or it won't work.
+
+            'VENDOR_SIMULATOR': "", # Simulator Vendor Name
+                                   # This is used for Live and Test transactions.  Make sure to activate
+                                   # the VSP Simulator (you have to directly request it) and add your
+                                   # server IP address to the VSP Simulator, or it won't work.")),
+
+            'CURRENCY_CODE': 'GBP',
+
+            'EXTRA_LOGGING': False,
+        }
+        working_settings.update(settings)            
+        super(PaymentProcessor, self).__init__('protx', working_settings)
+        self.require_settings('VENDOR')
+
+        if self.settings['SIMULATOR']:
+            try:
+                self.require_settings('VENDOR_SIMULATOR')
+            except GatewayError, ge:
+                self.log.warn("You are trying to use the Prot/X VSP Simulator, but you don't have a vendor name in settings for the simulator.")
+                raise ge
+                
+            vendor = self.settings['VENDOR_SIMULATOR']
+        else:
+            vendor = self.settings['VENDOR']
         
         self.packet = {
             'VPSProtocol': PROTOCOL,
-            'TxType': settings.CAPTURE.value,
+            'TxType': self.settings['CAPTURE'],
             'Vendor': vendor,
-            'Currency': settings.CURRENCY_CODE.value,
+            'Currency': self.settings['CURRENCY_CODE'],
             }
         self.valid = False
 
     def _url(self, key):
-        urls = PROTX_DEFAULT_URLS
-        if hasattr(settings, 'PROTX_URLS'):
-            urls.update(settings.PROTX_URLS)
-
-        if self.settings.SIMULATOR.value:
+        if self.settings['SIMULATOR']:
             key = "SIMULATOR_" + key
         else:
-            if self.settings.LIVE.value:
+            if self.is_live():
                 key = "LIVE_" + key
             else:
                 key = "TEST_" + key
-        return urls[key]
+        return self.settings[key]
 
-    def _connection(self):
+    @property
+    def connection(self):
         return self._url('CONNECTION')
-
-    connection = property(fget=_connection)        
         
-    def _callback(self):
+    @property
+    def callback(self):
         return self._url('CALLBACK')
-
-    callback = property(fget=_callback)
         
-    def prepare_post(self, data, amount):
+    def prepare_post(self, purchase, amount):
         
-        invoice = "%s" % data.id
-        failct = data.paymentfailures.count()
+        invoice = "%s" % purchase.id
+        failct = purchase.paymentfailures.count()
         if failct > 0:
             invoice = "%s_%i" % (invoice, failct)
         
         try:
-            cc = data.credit_card
-            balance = trunc_decimal(data.balance, 2)
+            cc = purchase.credit_card
+            balance = trunc_decimal(purchase.remaining, 2)
             self.packet['VendorTxCode'] = invoice
             self.packet['Amount'] = balance
             self.packet['Description'] = 'Online purchase'
                 self.packet['CV2'] = cc.ccv
             if cc.issue_num is not None and cc.issue_num != "":
                 self.packet['IssueNumber'] = cc.issue_num #'%02d' % int(cc.issue_num)
-            addr = [data.bill_street1, data.bill_street2, data.bill_city, data.bill_state]
+            addr = [purchase.bill_street1, purchase.bill_street2, purchase.bill_city, purchase.bill_state]
             self.packet['BillingAddress'] = ', '.join(addr)
-            self.packet['BillingPostCode'] = data.bill_postal_code
+            self.packet['BillingPostCode'] = purchase.bill_postal_code
         except Exception, e:
-            self.log.error('preparing data, got error: %s\nData: %s', e, data)
+            self.log.error('preparing data, got error: %s\nData: %s', e, purchase)
             self.valid = False
             return
             
         self.url = self.callback
         self.valid = True
         
-    def capture_payment(self, testing=False, order=None, amount=NOTSET):
+    def capture_payment(self, testing=False, purchase=None, amount=NOTSET):
         """Execute the post to protx VSP DIRECT"""
-        if not order:
-            order = self.order
+        if not purchase:
+            purchase = self.purchase
 
-        if order.paid_in_full:
-            self.log_extra('%s is paid in full, no capture attempted.', order)
-            self.record_payment()
+        if purchase.remaining == Decimal('0.00'):
+            self.log_extra('%s is paid in full, no capture attempted.', purchase)
+            self.record_payment(purchase=purchase)
             return ProcessorResult(self.key, True, _("No charge needed, paid in full."))
 
-        self.log_extra('Capturing payment for %s', order)
+        self.log_extra('Capturing payment for %s', purchase)
 
         if amount == NOTSET:
-            amount = order.balance
+            amount = purchase.remaining
 
-        self.prepare_post(order, amount)
+        self.prepare_post(purchase, amount)
         
         if self.valid:
-            if self.settings.SKIP_POST.value:
+            if self.settings['SKIP_POST']:
                 self.log.info("TESTING MODE - Skipping post to server.  Would have posted %s?%s", self.url, self.postString)
-                payment = self.record_payment(order=order, amount=amount, 
+                payment = self.record_payment(purchase=purchase, amount=amount, 
                     transaction_id="TESTING", reason_code='0')
 
                 return ProcessorResult(self.key, True, _('TESTING MODE'), payment=payment)
 
                 except urllib2.URLError, ue:
                     self.log.error("error opening %s\n%s", self.url, ue)
-                    return (False, 'ERROR', 'Could not talk to Protx gateway')
+                    return ProcessorResult(self.key, False, 'ERROR: Could not talk to Protx gateway')
 
                 try:
                     self.response = dict([row.split('=', 1) for row in result.splitlines()])
                     status = self.response['Status']
                     success = (status == 'OK')
                     detail = self.response['StatusDetail']
-                    
+                
                     payment = None
                     transaction_id = ""
                     if success:
                         vpstxid = self.response.get('VPSTxID', '')
                         txauthno = self.response.get('TxAuthNo', '')
                         transaction_id="%s,%s" % (vpstxid, txauthno)
-                        self.log.info('Success on order #%i, recording payment', self.order.id)
-                        payment = self.record_payment(order=order, amount=amount, 
+                        self.log.info('Success on purchase #%i, recording payment', self.purchase.id)
+                        payment = self.record_payment(purchase=purchase, amount=amount, 
                             transaction_id=transaction_id, reason_code=status)
-                            
+                        
                     else:
-                        payment = self.record_failure(order=order, amount=amount, 
+                        payment = self.record_failure(purchase=purchase, amount=amount, 
                             transaction_id=transaction_id, reason_code=status, 
                             details=detail)
 
 
                 except Exception, e:
                     self.log.info('Error submitting payment: %s', e)
-                    payment = self.record_failure(order=order, amount=amount, 
+                    payment = self.record_failure(purchase=purchase, amount=amount, 
                         transaction_id="", reason_code="error", 
                         details='Invalid response from bursar gateway')
                     

bursar/gateway/protx_gateway/tests.py

+# -*- coding: UTF-8 -*-
+"""Bursar Authorizenet Gateway Tests."""
+from bursar.gateway.protx_gateway import processor
+from bursar.errors import GatewayError
+from bursar.models import Authorization, Payment
+from bursar.tests import make_test_purchase
+from bursar.bursar_settings import get_bursar_setting
+from decimal import Decimal
+from django.conf import settings
+from django.contrib.sites.models import Site
+from django.core import urlresolvers
+from django.core.urlresolvers import reverse as url
+from django.test import TestCase
+from django.test.client import Client
+
+SKIP_TESTS = False
+NEED_SETTINGS = """Tests for protx_gateway module require a
+PROTX_TEST section in settings.BURSAR_SETTINGS.  At a 
+minimum, you must specify the VENDOR."""
+
+class TestGateway(TestCase):
+    def setUp(self):
+        global SKIP_TESTS
+        self.client = Client()
+        if not SKIP_TESTS:
+            settings = get_bursar_setting('PROTX_TEST', default_value=None)
+            if not settings:
+                SKIP_TESTS = True
+                raise GatewayError(NEED_SETTINGS)
+            settings['EXTRA_LOGGING'] = True
+            self.gateway = processor.PaymentProcessor(settings=settings)
+            self.default_payment = {
+                'card_holder' : 'Cave Man',
+                'ccv' : '144',
+                'card_number' : '4111111111111111',
+                'expire_month' : 12,
+                'expire_year' : 2012,
+                'card_type' : 'visa'
+            }
+
+    def tearDown(self):
+        pass
+        
+    # def test_authorize(self):
+    #     if SKIP_TESTS: return
+    #     purchase = make_test_purchase(sub_total=Decimal('20.00'), payment=self.default_payment)
+    #     result = self.gateway.authorize_payment(purchase=purchase)
+    #     self.assert_(result.success)
+    #     payment = result.payment
+    #     self.assertEqual(payment.amount, Decimal('20.00'))
+    #     self.assertEqual(purchase.total_payments, Decimal('0.00'))
+    #     self.assertEqual(purchase.authorized_remaining, Decimal('20.00'))
+
+    # def test_pending_authorize(self):
+    #     if SKIP_TESTS: return
+    #     purchase = make_test_purchase(sub_total=Decimal('20.00'), payment=self.default_payment)
+    #     self.assert_(purchase.credit_card)
+    #     pending = self.gateway.create_pending_payment(purchase)
+    #     self.assertEqual(pending.amount, Decimal('20.00'))
+    #     result = self.gateway.authorize_payment(purchase=purchase)
+    #     self.assert_(result.success)
+    #     payment = result.payment
+    #     self.assertEqual(payment.amount, Decimal('20.00'))
+    #     self.assertEqual(purchase.total_payments, Decimal('0.00'))
+    #     self.assertEqual(purchase.authorized_remaining, Decimal('20.00'))
+
+    def test_capture(self):
+        """Test making a direct payment using PROTX."""
+        if SKIP_TESTS: return
+        purchase = make_test_purchase(sub_total=Decimal('10.00'), payment=self.default_payment)
+        self.assertEqual(purchase.total, Decimal('10.00'))
+        result = self.gateway.capture_payment(purchase=purchase)
+        self.assert_(result.success)
+        payment = result.payment
+        self.assertEqual(payment.amount, Decimal('10.00'))
+        self.assertEqual(purchase.total_payments, Decimal('10.00'))
+        self.assertEqual(purchase.authorized_remaining, Decimal('0.00'))
+
+    # def test_authorize_multiple(self):
+    #     """Test making multiple authorization using PROTX."""
+    #     if SKIP_TESTS: return
+    #     purchase = make_test_purchase(sub_total=Decimal('100.00'), payment=self.default_payment)
+    #     self.assertEqual(purchase.total, Decimal('100.00'))
+    #     pending = self.gateway.create_pending_payment(purchase=purchase, amount=Decimal('25.00'))
+    #     self.assertEqual(pending.amount, Decimal('25.00'))
+    #     self.assertEqual(purchase.paymentspending.count(), 1)
+    #     pending2 = purchase.get_pending(self.gateway.key)
+    #     self.assertEqual(pending, pending2)
+    #     result = self.gateway.authorize_payment(purchase)
+    #     self.assertEqual(result.success, True)
+    #     self.assertEqual(purchase.authorized_remaining, Decimal('25.00'))
+    #     self.assertEqual(purchase.remaining, Decimal('75.00'))
+    # 
+    #     self.gateway.create_pending_payment(purchase=purchase, amount=Decimal('75.00'))
+    #     result = self.gateway.authorize_payment(purchase)
+    #     self.assert_(result.success)
+    #     auth = result.payment
+    #     self.assertEqual(auth.amount, Decimal('75.00'))
+    #     self.assertEqual(purchase.authorized_remaining, Decimal('100.00'))
+    # 
+    #     results = self.gateway.capture_authorized_payments(purchase)
+    #     self.assertEqual(len(results), 2)
+    #     r1 = results[0]
+    #     r2 = results[1]
+    #     self.assertEqual(r1.success, True)
+    #     self.assertEqual(r2.success, True)
+    #     self.assertEqual(purchase.total_payments, Decimal('100'))
+
+    def test_multiple_pending(self):
+        """Test that creating a second pending payment deletes the first one."""
+        if SKIP_TESTS: return
+        purchase = make_test_purchase(sub_total=Decimal('125.00'), payment=self.default_payment)
+        self.assertEqual(purchase.total, Decimal('125.00'))
+        pend1 = self.gateway.create_pending_payment(purchase=purchase, amount=purchase.total)
+        pend2 = self.gateway.create_pending_payment(purchase=purchase, amount=purchase.total)
+    
+        self.assertEqual(purchase.paymentspending.count(), 1)
             expire_month=payment['expire_month'],
             expire_year=payment['expire_year'],
         )
+        if 'card_holder' in payment:
+            c.card_holder = payment['card_holder']
         c.storeCC(payment['card_number'])
         c.ccv = payment['ccv']
         c.save()        

projects/tests/local_settings.py

          'TRANKEY': "Ww/xasElmB5X5FTwYLH/Bnb8dXwNp+/aOogskyIApmZ2j0Ly0zl7fu2rE2Mla/8VyQJAUeS+YZ02M3obf2bhzsrIb7fEsH5ONBm6RXu/H9aXf/XxTvKu9af5Nnsi2a9bGwTl27giGdcpW9oSgX1jUryQ1zCCAnYCAQAwDQYJKob+qet4f8O2yLSzhwEBBQAEggJgMIICXAIBAAKBgQCV+GCZCP21UYPwoFSOFAir49ag658dFJOqAC+6qeZ51Nr9D1jEJLw6WF1hY6kk4BAZ3K9PptvTj3pG7NY3TmrKlmI3RbMKBYIUVrKnku91mBqDxJupmXZRSZ7i+2Zvl8oTHQ==",
          'EXTRA_LOGGING': True
      },
+     'PROTX_TEST' : {
+        'VENDOR' : 'TEST',
+     }
 }
 
 #Configure logging

projects/tests/settings.py

     'payment.modules.cybersource',
     'bursar.gateway.dummy_gateway',
     'payment.modules.dummy',
+    'bursar.gateway.protx_gateway',
+    'payment.modules.protx',
     'bursar.gateway.purchaseorder_gateway',
     'payment.modules.purchaseorder',
     'bursar.gateway.paypal_gateway',
     'payment.modules.paypal',
-    #'payment.modules.purchaseorder',
-    #'payment.modules.giftcertificate',
-    #create-'satchmo_ext.wishlist',
-    #'satchmo_ext.upsell',
-    #'satchmo_ext.productratings',
     'satchmo_ext.satchmo_toolbar',
     'satchmo_utils',
-    #'shipping.modules.tieredquantity',
     #'django_extensions',
-    #'satchmo_ext.tieredpricing',
-    #'typogrify',
     #'debug_toolbar',
     'app_plugins',
     'tests.localsite',