diff --git a/gms/gms/settings.py b/gms/gms/settings.py
index e620d088986454e171b94d64bff9232c4ddfcda1..27aec658323ac20aee3e26def72a2297cf1b1093 100644
--- a/gms/gms/settings.py
+++ b/gms/gms/settings.py
@@ -33,6 +33,7 @@ MIDDLEWARE = [
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'memberdb.views.MemberMiddleware',
 ]
 
 ROOT_URLCONF = 'gms.urls'
@@ -139,5 +140,9 @@ LOGGING = {
             'level': LOG_LEVEL,
             'handlers': ['logfile', 'console'],
         },
+        'squarepay': {
+            'level': LOG_LEVEL,
+            'handlers': ['logfile', 'console'],
+        }
     },
-}
\ No newline at end of file
+}
diff --git a/gms/gms/settings_local.example.py b/gms/gms/settings_local.example.py
index 08919b4c64caaed4fb86cea24b51777bfeac1ad6..89de59e13e31173bb65d2d114a6d06f7f5bd7b62 100644
--- a/gms/gms/settings_local.example.py
+++ b/gms/gms/settings_local.example.py
@@ -89,4 +89,13 @@ AUTH_LDAP_USER_FLAGS_BY_GROUP = {
 # the Square app and location data (set to sandbox unless you want it to charge people)
 SQUARE_APP_ID = 'maybe-sandbox-something-something-here'
 SQUARE_LOCATION = 'CBASEDE-this-is-probably-somewhere-in-Sydney'
-SQUARE_ACCESS_TOKEN = 'keep-this-very-secret'
\ No newline at end of file
+SQUARE_ACCESS_TOKEN = 'keep-this-very-secret'
+
+DISPENSE_BIN = '/usr/local/bin/dispense'
+
+# configure the email backend (see https://docs.djangoproject.com/en/2.1/topics/email/)
+EMAIL_HOST = "secure.ucc.asn.au"
+EMAIL_PORT = 465
+EMAIL_USE_SSL = True
+EMAIL_HOST_USER = "uccportal"
+EMAIL_HOST_PASSWORD = "changeme"
diff --git a/gms/squarepay/dispense.py b/gms/squarepay/dispense.py
new file mode 100644
index 0000000000000000000000000000000000000000..42b623461e8f51c3f55658829475f4150628ca87
--- /dev/null
+++ b/gms/squarepay/dispense.py
@@ -0,0 +1,48 @@
+"""
+this file contains utilities for wrapping the opendispense2 CLI utility `dispense`
+It is essentially a hack to avoid having to write an actual dispense client here.
+"""
+
+import subprocess
+from subprocess import CalledProcessError, TimeoutExpired
+from django.conf import settings
+
+from .payments import log
+
+DISPENSE_BIN = getattr(settings, 'DISPENSE_BIN', None)
+
+if DISPENSE_BIN is None:
+    log.warning("DISPENSE_BIN is not defined! Lookups for prices will fail!")
+
+def run_dispense(*args):
+    if DISPENSE_BIN is None:
+        return None
+    
+    cmd = [DISPENSE_BIN] + args
+    log.info("run_dispense: " + cmd)
+    try:
+        # get a string containing the output of the program
+        res = subprocess.check_output(cmd, timeout=4, universal_newlines=True)
+    except CalledProcessError as e:
+        log.warning("dispense returned error code %d, output: '%s'" % (e.returncode, e.output))
+        return None
+    except TimeoutExpired as e:
+        log.error(e)
+        return None
+    return res
+
+def get_item_price(itemid):
+    """ gets the price of the given dispense item in cents """
+    if (itemid is None or itemid == ""):
+        return None
+    out = run_dispense('iteminfo', itemid)
+    if (out is None):
+        return 123456
+    
+    s = out.split() # get something like ['pseudo:7', '25.00', 'membership', '(non-student', 'and', 'non-guild)']
+    if (s[0] != itemid):
+        log.warning("get_item_price: got result for incorrect item: %s" + s)
+        return None
+    else:
+        # return the price as a number of cents
+        return int(float(s[0]) * 100)
\ No newline at end of file
diff --git a/gms/squarepay/payments.py b/gms/squarepay/payments.py
new file mode 100644
index 0000000000000000000000000000000000000000..f64b6d9b824d94f5de311d1e64595c2163ede11f
--- /dev/null
+++ b/gms/squarepay/payments.py
@@ -0,0 +1,62 @@
+"""
+This file contains functions for dealing with payments (although that's fairly obvious)
+"""
+import logging
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils import timezone
+
+import squareconnect
+from squareconnect.rest import ApiException
+from squareconnect.apis.transactions_api import TransactionsApi
+
+log = logging.getLogger('squarepay')
+
+# load the configuration values
+app_id = getattr(settings, 'SQUARE_APP_ID', None)
+loc_id = getattr(settings, 'SQUARE_LOCATION', None)
+access_key = getattr(settings, 'SQUARE_ACCESS_TOKEN', None)
+
+# make sure the configuration values exist
+if (app_id is None) or (loc_id is None) or (access_key is None):
+    raise ImproperlyConfigured("Please define SQUARE_APP_ID, SQUARE_LOCATION and SQUARE_ACCESS_TOKEN in settings.py")
+
+# instantiate the global squareconnect ApiClient instance (only needs to be done once)
+_sqapi_inst = squareconnect.ApiClient()
+_sqapi_inst.configuration.access_token = access_key
+
+def get_transactions_api():
+    return TransactionsApi(_sqapi_inst)
+
+def try_capture_payment(card_payment, nonce):
+    """
+    attempt to charge the customer associated with the given card nonce (created by the PaymentForm in JS)
+    Note: this can be called multiple times with the same CardPayment instance but the customer will not
+    be charged multiple times (using the Square idempotency key feature)
+    Returns either True on success or False on failure.
+    """
+    api_inst = get_transactions_api()
+
+    request_body = {
+        'idempotency_key': card_payment.idempotency_key,
+        'card_nonce': nonce,
+        'amount_money': {
+            'amount': card_payment.amount,
+            'currency': 'AUD'
+        }
+    }
+
+    try:
+        api_response = api_inst.charge(loc_id, request_body)
+        set_paid(card_payment)
+        log.info("TransactionApi response without error, charge $%1.2f" % (float(card_payment.amount) / 100.0))
+        return True
+    except ApiException as e:
+        log.error("Exception while calling TransactionApi::charge: %s" % e)
+        return False
+
+def set_paid(card_payment):
+    card_payment.is_paid = True
+    card_payment.date_paid = timezone.now()
+    card_payment.save()