diff --git a/gms/squarepay/admin.py b/gms/squarepay/admin.py
index 7f8f4f2ddd5254b6744ffe1e1c5bf99151003a29..a69a0ddffe437bf588b1e707b483444e0db05262 100644
--- a/gms/squarepay/admin.py
+++ b/gms/squarepay/admin.py
@@ -1,7 +1,7 @@
 from django.utils.html import format_html
 
 from gms import admin
-from .models import CardPayment
+from .models import CardPayment, MembershipPayment
 
 class CardPaymentAdmin(admin.ModelAdmin):
     list_display = ['amount', 'url_field', 'date_created', 'is_paid']
@@ -12,4 +12,8 @@ class CardPaymentAdmin(admin.ModelAdmin):
     url_field.short_description = 'Payment URL'
     url_field.allow_tags = True
 
-admin.site.register(CardPayment, CardPaymentAdmin)
\ No newline at end of file
+class MembershipPaymentAdmin(CardPaymentAdmin):
+    list_display = ['amount', 'url_field', 'date_created', 'is_paid', 'membership']
+
+admin.site.register(CardPayment, CardPaymentAdmin)
+admin.site.register(MembershipPayment, MembershipPaymentAdmin)
diff --git a/gms/squarepay/models.py b/gms/squarepay/models.py
index 7000231201cd48f1e9dda1d868064f2fc37742a5..4a3a420259e5b184fe222ac80973322ac3f43193 100644
--- a/gms/squarepay/models.py
+++ b/gms/squarepay/models.py
@@ -1,24 +1,25 @@
 import uuid
-from django.core.management.utils import get_random_secret_key
+
+from django.core.management.utils import get_random_string
 from django.db import models
 from django.urls import reverse
 
+from memberdb.models import Membership, make_token
+
 class CardPayment(models.Model):
-    token           = models.CharField('Unique payment token', max_length=64, editable=False, default=get_random_secret_key)
     description     = models.CharField('Description', max_length=255)
     amount          = models.IntegerField('Amount in cents', null=False, blank=False)
-    idempotency_key = models.CharField('Square Transactions API idempotency key', max_length=64, editable=False, default=uuid.uuid1)
+    idempotency_key = models.CharField('Square Transactions API idempotency key', max_length=64, default=uuid.uuid1)
     is_paid         = models.BooleanField('Has been paid', blank=True, default=False)
-    completed_url   = models.CharField('Redirect URL on success', max_length=255, null=True, editable=False)
-    dispense_synced = models.BooleanField('Payment lodged in dispense', blank=True, default=False)
+    dispense_synced = models.BooleanField('Payment logged in dispense', blank=True, default=False)
     date_created    = models.DateTimeField('Date created', auto_now_add=True)
     date_paid       = models.DateTimeField('Date paid (payment captured)', null=True, blank=True)
 
-    def save(self, *args, **kwargs):
-        # generate a token by default. maybe possible using default=...?
-        if (self.token is None):
-            self.token = get_random_secret_key()
-        super().save(*args, **kwargs)
-
     def get_absolute_url(self):
-        return reverse('squarepay:pay', kwargs={ 'pk': self.pk, 'token': self.token })
\ No newline at end of file
+        return reverse('squarepay:pay', kwargs={ 'pk': self.pk, 'token': self.token })
+
+class MembershipPayment(CardPayment):
+    """
+    Link the payment to a specific membership
+    """
+    membership      = models.ForeignKey(Membership, on_delete=models.CASCADE, related_name='payments')
diff --git a/gms/squarepay/urls.py b/gms/squarepay/urls.py
index b31bd3166a46e8834b99639215daccc48ec19f2b..607cfed6e56898cc60d24452cb3688480f0eeac5 100644
--- a/gms/squarepay/urls.py
+++ b/gms/squarepay/urls.py
@@ -1,8 +1,9 @@
 from django.urls import path
 
-from .views import PaymentFormView
+from .views import PaymentFormView, MembershipPaymentView
 
 app_name = 'squarepay'
 urlpatterns = [
-    path('pay/<int:pk>/<str:token>/', PaymentFormView.as_view(), name='pay'),
-]
\ No newline at end of file
+    #path('pay/<int:pk>/<str:token>/', PaymentFormView.as_view(), name='pay'),
+    path('pay/<int:pk>/<str:token>/', MembershipPaymentView.as_view(), name='pay'),
+]
diff --git a/gms/squarepay/views.py b/gms/squarepay/views.py
index 3ee1d09619105c68b464d8908009dc58d94cfa09..467aae1cde4dd31edde1073595d66ef41a9d05d2 100644
--- a/gms/squarepay/views.py
+++ b/gms/squarepay/views.py
@@ -1,15 +1,15 @@
 import uuid
+from django.views.generic.base import RedirectView
 from django.views.generic.detail import DetailView
 from django.http import HttpResponse, HttpResponseRedirect, Http404
 from django.contrib import messages
 from django.conf import settings
+from django.urls import reverse
+from django.utils import timezone
 
-import squareconnect
-from squareconnect.rest import ApiException
-from squareconnect.apis.transactions_api import TransactionsApi
-from squareconnect.apis.locations_api import LocationsApi
-
-from .models import CardPayment
+from .models import MembershipPayment, CardPayment
+from . import payments
+from .payments import try_capture_payment, set_paid
 
 class PaymentFormView(DetailView):
     """
@@ -19,11 +19,6 @@ class PaymentFormView(DetailView):
 
     template_name = 'payment_form.html'
 
-    app_id = None       # square app ID (can be accessed by clients)
-    loc_id = None       # square location key (can also be accessed by clients)
-    access_key = None   # this is secret
-    sqapi = None        # keep an instance of the Square API handy
-
     model = CardPayment
     slug_field = 'token'
     slug_url_kwarg = 'token'
@@ -33,51 +28,58 @@ class PaymentFormView(DetailView):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        # get things from settings
-        self.app_id = getattr(settings, 'SQUARE_APP_ID', 'bad_config')
-        self.loc_id = getattr(settings, 'SQUARE_LOCATION', 'bad_config')
-        self.access_key = getattr(settings, 'SQUARE_ACCESS_TOKEN')
-
-        # do some square API client stuff
-        self.sqapi = squareconnect.ApiClient()
-        self.sqapi.configuration.access_token = self.access_key
-
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
+        amount = "$%1.2f AUD" % (self.get_object().amount / 100.0)
         context.update({
-            'app_id': self.app_id,
-            'loc_id': self.loc_id,
+            'app_id': payments.app_id,
+            'loc_id': payments.loc_id,
+            'amount': amount,
         })
         return context
 
-    def post(self, request, *args, **kwargs):
-        nonce = request.POST.get('nonce', None)
-        if (nonce is None or nonce == ""):
-            messages.error(request, "No nonce was generated! Please try reloading the page and submit again.")
-            return self.get(request)
+    def payment_success(self, payment):
+        set_paid(payment)
 
-        api_inst = TransactionsApi(self.sqapi)
+    def get_completed_url(self):
+        return self.get_object().get_absolute_url()
 
-        body = {
-            'idempotency_key': self.idempotency_key,
-            'card_nonce': nonce,
-            'amount_money': {
-                'amount': amount,
-                'currency': 'AUD'
-            }
-        }
+    def post(self, request, *args, **kwargs):
+        nonce = request.POST.get('nonce', None)
+        card_payment = self.get_object()
+        amount_aud = card_payment.amount / 100.0
 
-        try:
-            api_response = api_inst.charge(self.loc_id, body)
-            messages.success(request, "Your payment of %1.2f was successful.", amount)
-        except ApiException as e:
-            messages.error(request, "Exception while calling TransactionApi::charge: %s" % e)
-        
-        # redirect to success URL
-        if (self.object.completed_url is None):
+        if (nonce is None or nonce == ""):
+            messages.error(request, "Failed to collect card details. Please reload the page and submit again.")
             return self.get(request)
-        return HttpResponseRedirect(self.object.completed_url)
-
 
+        if try_capture_payment(card_payment, nonce):
+            payment_success(card_payment)
+            messages.success(request, "Your payment of $%1.2f was successful." % amount_aud)
+        else:
+            messages.error(request, "Your payment of $%1.2f was unsuccessful. Please try again later." % amount_aud)
         
-
+        # redirect to success URL, or redisplay the form with a success message if none is given
+        return HttpResponseRedirect(self.get_completed_url())
+
+class MembershipPaymentView(PaymentFormView):
+    model = MembershipPayment
+
+    def dispatch(self, request, *args, **kwargs):
+        self.object = self.get_object()
+        if (self.object.membership.date_paid is not None):
+            # the membership is already marked as paid, so we add an error and redirect to member home
+            messages.error(request, "Your membership is already paid. Check the cokelog (/home/other/coke/cokelog) for more details.")
+            return HttpResponseRedirect(self.get_completed_url())
+        else:
+            return super().dispatch(request, *args, **kwargs)
+
+    def payment_success(self, payment):
+        ms = payment.membership
+        ms.date_paid = timezone.now()
+        ms.payment_method = 'online'
+        ms.save()
+        super().payment_success(payment)
+
+    def get_completed_url(self):
+        return reverse('memberdb:home')