diff --git a/gms/squarepay/admin.py b/gms/squarepay/admin.py
index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..7f8f4f2ddd5254b6744ffe1e1c5bf99151003a29 100644
--- a/gms/squarepay/admin.py
+++ b/gms/squarepay/admin.py
@@ -1,3 +1,15 @@
-from django.contrib import admin
+from django.utils.html import format_html
 
-# Register your models here.
+from gms import admin
+from .models import CardPayment
+
+class CardPaymentAdmin(admin.ModelAdmin):
+    list_display = ['amount', 'url_field', 'date_created', 'is_paid']
+    readonly_fields = ['token', 'idempotency_key']
+
+    def url_field(self, obj):
+        return format_html('<a href="{}">Goto payment page</a>', obj.get_absolute_url())
+    url_field.short_description = 'Payment URL'
+    url_field.allow_tags = True
+
+admin.site.register(CardPayment, CardPaymentAdmin)
\ No newline at end of file
diff --git a/gms/squarepay/models.py b/gms/squarepay/models.py
index 71a836239075aa6e6e4ecb700e9c42c95c022d91..7000231201cd48f1e9dda1d868064f2fc37742a5 100644
--- a/gms/squarepay/models.py
+++ b/gms/squarepay/models.py
@@ -1,3 +1,24 @@
+import uuid
+from django.core.management.utils import get_random_secret_key
 from django.db import models
+from django.urls import reverse
 
-# Create your models here.
+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)
+    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)
+    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
diff --git a/gms/squarepay/static/squarepay.css b/gms/squarepay/static/squarepay.css
index 4856870e9837a285167a27a1c67bfc3ec51c7882..f84f01fa02f65cf6c8b9f1a4f6f365d0fb43a26f 100644
--- a/gms/squarepay/static/squarepay.css
+++ b/gms/squarepay/static/squarepay.css
@@ -29,30 +29,36 @@ fieldset {
     text-transform: uppercase;
 }
 
-.third {
+.half {
     float: left;
-    width: calc((100% - 32px) / 3);
+    width: calc((100% - 16px) / 2);
     padding: 0;
-    margin: 0 16px 16px 0;
+    margin-right: 16px;
 }
 
-.third:last-of-type {
+.half:last-of-type {
     margin-right: 0;
 }
 
 /* Define how SqPaymentForm iframes should look */
-.sq-input {
+.sq-input,
+#sq-expiration-date, #sq-card-number, #sq-cvv {
     box-sizing: border-box;
     border: 1px solid #E0E2E3;
     border-radius: 4px;
     outline-offset: -2px;
-    display: inline-block;
+    /*display: inline-block;*/
+    height: 56px;
     -webkit-transition: border-color .2s ease-in-out, background .2s ease-in-out;
          -moz-transition: border-color .2s ease-in-out, background .2s ease-in-out;
             -ms-transition: border-color .2s ease-in-out, background .2s ease-in-out;
                     transition: border-color .2s ease-in-out, background .2s ease-in-out;
 }
 
+div#sq-expiration-date, div#sq-card-number, div#sq-cvv {
+    background-color: #f2f2f2;
+}
+
 /* Define how SqPaymentForm iframes should look when they have focus */
 .sq-input--focus {
     border: 1px solid #4A90E2;
@@ -72,6 +78,22 @@ fieldset {
 
 /* Customize the "Pay with Credit Card" button */
 .button-credit-card {
+    width: 100%;
+    height: 56px;
+    margin-top: 10px;
+    background: #ccf;
+    border-radius: 4px;
+    cursor: default;
+    display: block;
+    color: #FFFFFF;
+    font-size: 16px;
+    line-height: 24px;
+    font-weight: 700;
+    letter-spacing: 0;
+    text-align: center;
+
+}
+.button-credit-card:enabled {
     width: 100%;
     height: 56px;
     margin-top: 10px;
@@ -91,11 +113,28 @@ fieldset {
                     transition: background .2s ease-in-out;
 }
 
-.button-credit-card:hover {
+.button-credit-card:hover:enabled {
     background-color: #4281CB;
 }
 
+#form-container {
+    position: relative;
+    width: 380px;
+    margin: 20px auto;
+    box-sizing: content-box;
+    border: 2px solid #eee;
+    background-color: white;
+    padding: 16px ;
+}
 
+.form-row {
+    margin: 0;
+}
+
+/*#content, #container, body {
+    height: 100%;
+}
+*/
 /* Customize the Apple Pay on the Web button */
 #sq-apple-pay {
     width: 100%;
@@ -120,7 +159,7 @@ fieldset {
     margin: 24px 0 24px;
     background-image: url("https://masterpass.com/dyn/img/acc/global/mp_mark_hor_wht.svg");
     background-color: black;
-    background-size: 100% 60%;
+    background-size: 60% 60%;
     background-repeat: no-repeat;
     background-position: calc((100% - 32px) / 2) 50%;
     border-radius: 4px;
@@ -128,7 +167,7 @@ fieldset {
     display: none;
 }
 
-#sq-masterpass::after {
+/*#sq-masterpass::after {
     box-sizing: border-box;
     float: right;
     width: 32px;
@@ -137,7 +176,7 @@ fieldset {
     content: url("data:image/svg+xml; utf8, <svg width='14' height='24' viewBox='0 0 14 24' xmlns='http://www.w3.org/2000/svg'><path d='M1.891 23.485c-.389 0-.778-.144-1.075-.436a1.46 1.46 0 0 1 0-2.102l9.141-8.944L.817 3.06a1.463 1.463 0 0 1 0-2.104 1.544 1.544 0 0 1 2.15 0l10.217 9.994a1.464 1.464 0 0 1 0 2.105L2.966 23.049a1.525 1.525 0 0 1-1.075.436' fill='#FFF' fill-rule='evenodd'/></svg>");
     background-color: #E6761F;
     border-radius: 0 4px 4px 0;
-}
+}*/
 
 /* Customize the Google Pay button */
 .button-google-pay {
@@ -159,39 +198,6 @@ fieldset {
     display: none;
 }
 
-
-#sq-walletbox {
-    margin: 0;
-    padding: 0;
-    text-align: center;
-    vertical-align: top;
-    width:100%;
-    height:123;
-    display:none;
-}
-
-#sq-walletbox-divider {
-    height: 24px;
-    margin: 24px 0 24px;
-    color: #CCC;
-    font-size: 14px;
-    font-weight: bold;
-    line-height: 24px;
-    letter-spacing: 0.5;
-    text-align: center;
-    text-transform: uppercase;
-    width: 100%;
-}
-
-#sq-walletbox-divider-label {
-    padding: 0 24px;
-    background-color: #FFF;
-}
-
-#sq-walletbox-divider hr {
-    margin-top: -12px;
-}
-
 #error {
     width: 100%;
     margin-top: 16px;
@@ -200,4 +206,5 @@ fieldset {
     font-weight: 500;
     text-align: center;
     opacity: 0.8;
+    display: none;
 }
diff --git a/gms/squarepay/static/squarepay.js b/gms/squarepay/static/squarepay.js
index 7b264ce5760d9d8a7a33c783584db1c030019fd7..38c6f3e45fa58df6081e1221373f34197da99506 100644
--- a/gms/squarepay/static/squarepay.js
+++ b/gms/squarepay/static/squarepay.js
@@ -1,5 +1,6 @@
 /** Javascript to handle the Square payments form.
- * Adapted from https://docs.connect.squareup.com/payments/sqpaymentform/setup */
+ * Adapted from https://docs.connect.squareup.com/payments/sqpaymentform/setup
+ * SqPaymentForm documentation at https://docs.connect.squareup.com/api/paymentform */
 
 /*
  * function: requestCardNonce
@@ -41,20 +42,10 @@ var paymentForm = new SqPaymentForm({
         _mozOsxFontSmoothing: 'grayscale'
     }],
 
-    // Initialize Apple Pay placeholder ID
-    applePay: {
-        elementId: 'sq-apple-pay'
-    },
-
-    // Initialize Masterpass placeholder ID
-    masterpass: {
-        elementId: 'sq-masterpass'
-    },
-
-    // Initialize Google Pay placeholder ID
-    googlePay: {
-        elementId: 'sq-google-pay'
-    },
+    /* digital wallets are only supported by Square for accounts in the US :'( */
+    applePay: false,
+    masterpass: false,
+    googlePay: false,
 
     // Initialize the credit card placeholders
     cardNumber: {
@@ -69,43 +60,15 @@ var paymentForm = new SqPaymentForm({
         elementId: 'sq-expiration-date',
         placeholder: 'MM/YY'
     },
-    postalCode: {
-        elementId: 'sq-postal-code',
-        placeholder: '6009'
-    },
+
+    /* postal code is not required in AU */
+    postalCode: false,
 
     // SqPaymentForm callback functions
     callbacks: {
-        /* callback function: methodsSupported
-         * Triggered when: the page is loaded. */
-        methodsSupported: function (methods) {
-            var walletBox = document.getElementById('sq-walletbox');
-            var applePayBtn = document.getElementById('sq-apple-pay');
-            var googlePayBtn = document.getElementById('sq-google-pay');
-            var masterpassBtn = document.getElementById('sq-masterpass');
-
-            // Only show the button if Apple Pay for Web is enabled
-            // Otherwise, display the wallet not enabled message.
-            if (methods.applePay === true) {
-                walletBox.style.display = 'block';
-                applePayBtn.style.display = 'block';
-            }
-            // Only show the button if Masterpass is enabled
-            // Otherwise, display the wallet not enabled message.
-            if (methods.masterpass === true) {
-                walletBox.style.display = 'block';
-                masterpassBtn.style.display = 'block';
-            }
-            // Only show the button if Google Pay is enabled
-            if (methods.googlePay === true) {
-                walletBox.style.display = 'block';
-                googlePayBtn.style.display = 'inline-block';
-            }
-        },
-
         /* callback function: createPaymentRequest
          * Triggered when: a digital wallet payment button is clicked. */
-        createPaymentRequest: function () {
+/*        createPaymentRequest: function () {
 
             return {
                 requestShippingAddress: false,
@@ -125,27 +88,16 @@ var paymentForm = new SqPaymentForm({
                     }
                 ]
             }
-        },
-
-        /* callback function: validateShippingContact
-         * Triggered when: a shipping address is selected/changed in a digital
-         *                                 wallet UI that supports address selection. */
-        validateShippingContact: function (contact) {
-
-            var validationErrorObj;
-            /* ADD CODE TO SET validationErrorObj IF ERRORS ARE FOUND */
-            return validationErrorObj;
-        },
+        },*/
 
         /* callback function: cardNonceResponseReceived
          * Triggered when: SqPaymentForm completes a card nonce request */
         cardNonceResponseReceived: function (errors, nonce, cardData) {
             if (errors) {
                 // Log errors from nonce generation to the Javascript console
-                console.log("Encountered errors:");
+                console.log("cardNonceResponseReceived encountered errors:");
                 errors.forEach(function (error) {
                     console.log('    ' + error.message);
-                    alert(error.message);
                 });
 
                 return;
@@ -155,7 +107,6 @@ var paymentForm = new SqPaymentForm({
 
             // POST the nonce form to the payment processing page
             document.getElementById('nonce-form').submit();
-
         },
 
         /* callback function: unsupportedBrowserDetected
@@ -167,6 +118,7 @@ var paymentForm = new SqPaymentForm({
         /* callback function: inputEventReceived
          * Triggered when: visitors interact with SqPaymentForm iframe elements. */
         inputEventReceived: function (inputEvent) {
+            var e = document.getElementById("error");
             switch (inputEvent.eventType) {
                 case 'focusClassAdded':
                     /* HANDLE AS DESIRED */
@@ -175,11 +127,12 @@ var paymentForm = new SqPaymentForm({
                     /* HANDLE AS DESIRED */
                     break;
                 case 'errorClassAdded':
-                    document.getElementById("error").innerHTML = "Please fix card information errors before continuing.";
+                    e.innerHTML = "Please fix card information errors before continuing.";
+                    e.style.display = "block";
                     break;
                 case 'errorClassRemoved':
                     /* HANDLE AS DESIRED */
-                    document.getElementById("error").style.display = "none";
+                    e.style.display = "none";
                     break;
                 case 'cardBrandChanged':
                     /* HANDLE AS DESIRED */
@@ -195,13 +148,19 @@ var paymentForm = new SqPaymentForm({
         paymentFormLoaded: function () {
             /* HANDLE AS DESIRED */
             console.log("The form loaded!");
+            btn = document.getElementById("sq-creditcard");
+            btn.disabled = false;
         }
     }
 });
 
 document.addEventListener("DOMContentLoaded", function(event) {
-    if (SqPaymentForm.isSupportedBrowser()) {
+    /* for testing, you can add ...?unsupported to the URL */
+    if (SqPaymentForm.isSupportedBrowser() && !window.location.href.includes("unsupported")) {
+        console.log("loading Square payment form...");
         paymentForm.build();
         paymentForm.recalculateSize();
+    } else {
+        console.log("not loading form: unsupported browser!");
     }
 });
\ No newline at end of file
diff --git a/gms/squarepay/templates/payment_form.html b/gms/squarepay/templates/payment_form.html
index dab48743873a3ac9cda853b6b12bfa25f4df1eb4..51751cf7f3ec5607b0f6d0446d23479648e3ad30 100644
--- a/gms/squarepay/templates/payment_form.html
+++ b/gms/squarepay/templates/payment_form.html
@@ -1,6 +1,8 @@
 {% extends "base.html" %}
 {% load static %}
 
+{% block title %}UCC Payment Gateway{% endblock %}
+
 {% block extrahead %}
 <!-- link to the SqPaymentForm library -->
 <script type="text/javascript" src="https://js.squareup.com/v2/paymentform"></script>
@@ -9,74 +11,67 @@
 <script type="text/javascript"> 
 var applicationId = "{{ app_id }}";
 var locationId = "{{ loc_id }}";
+var amount = {{ payment.amount }};
 </script>
 
 <script type="text/javascript" src="{% static 'squarepay.js' %}"></script>
 <link rel="stylesheet" type="text/css" href="{% static 'squarepay.css' %}">
 {% endblock %}
 
-{% block content %}
-{% if response %}
-<h1>{{ response }}</h1>
-{% endif %}
 
+{% block content_title %}<h1>Pay with card</h1>{% endblock %}
+{% block content %}
+<div class="form-container">
+<div class="form-header">
+    <span class="tips">{% block tips %}<b><i>Please fill in your card details below.</i></b>{% endblock %}</span>
+    {% if payment.is_paid %}<span class="tips">
+       <p><b>It appears you have already successfully attempted this payment.</b><br><br>
+        Note that you will not be charged twice if even you submit again.</p>
+    </span>{% endif %}
+    <noscript>
+        <span class="tips error">Please enable javascript to use the payment form.</span>
+    </noscript>
+</div>
+<!-- the element #form-container is used to create the Square payment form (hardcoded) -->
 <div id="form-container">
-<div id="sq-walletbox">
-    <!-- Placeholder for Apple Pay for Web button -->
-    <button id="sq-apple-pay"></button>
-
-    <!-- Placeholder for Masterpass button -->
-    <button id="sq-masterpass"></button>
-
-    <!-- Placeholder for Google Pay button-->
-    <button id="sq-google-pay" class="button-google-pay"></button>
 
-    <div id="sq-walletbox-divider">
-    <span id="sq-walletbox-divider-label">Or</span>
-    <hr />
-    </div>
-</div>
-<div id="sq-ccbox">
-    <!--
-    Be sure to replace the action attribute of the form with the path of
-    the Transaction API charge endpoint URL you want to POST the nonce to
-    (for example, "/process-card")
-    -->
-    <form id="nonce-form" novalidate action="{% url 'squarepay:pay' %}" method="post">
-    {% csrf_token %}
-    <fieldset>
-        <div class="form-row">
-            <span class="label">Card Number</span>
-            <div id="sq-card-number"></div>
-        </div>
+    <div id="sq-ccbox">
+        <!--
+        Be sure to replace the action attribute of the form with the path of
+        the Transaction API charge endpoint URL you want to POST the nonce to
+        (for example, "/process-card")
+        -->
+        <form id="nonce-form" novalidate action="#" method="post">
+        {% csrf_token %}
+        <div id="error"></div>
 
-        <div class="form-row">
-            <div class="third">
-            <span class="label">Expiration</span>
-            <div id="sq-expiration-date"></div>
+        <fieldset>
+            <div class="form-row">
+                <span class="label">Card Number</span>
+                <div id="sq-card-number"></div>
             </div>
 
-            <div class="third">
-            <span class="label">CVV</span>
-            <div id="sq-cvv"></div>
-            </div>
+            <div class="form-row">
+                <div class="half">
+                <span class="label">Expiration</span>
+                <div id="sq-expiration-date"></div>
+                </div>
 
-            <div class="third">
-            <span class="label">Postcode</span>
-            <div id="sq-postal-code"></div>
+                <div class="half">
+                <span class="label">CVV</span>
+                <div id="sq-cvv"></div>
+                </div>
             </div>
-        </div>
-    </fieldset>
-
-    <button id="sq-creditcard" class="button-credit-card" onclick="requestCardNonce(event)">Pay $1.00</button>
+        </fieldset>
+        <button id="sq-creditcard" class="button-credit-card" onclick="requestCardNonce(event)" disabled>Pay with card</button>
 
-    <div id="error"></div>
-
-    <!--
-        After a nonce is generated it will be assigned to this hidden input field.
-    -->
-    <input type="hidden" id="card-nonce" name="nonce">
-    </form>
-</div> <!-- end #sq-ccbox -->
+        <!--
+            After a nonce is generated it will be assigned to this hidden input field.
+        -->
+        <input type="hidden" id="card-nonce" name="nonce">
+        </form>
+    </div> <!-- end #sq-ccbox -->
 </div> <!-- end #form-container -->
+</div>
+</div>
 {% endblock %}
\ No newline at end of file
diff --git a/gms/squarepay/urls.py b/gms/squarepay/urls.py
index 59c1b711f2de2402322302532cdfab7fc20ad612..b31bd3166a46e8834b99639215daccc48ec19f2b 100644
--- a/gms/squarepay/urls.py
+++ b/gms/squarepay/urls.py
@@ -4,5 +4,5 @@ from .views import PaymentFormView
 
 app_name = 'squarepay'
 urlpatterns = [
-    path('pay/', PaymentFormView.as_view(), name='pay'),
+    path('pay/<int:pk>/<str:token>/', PaymentFormView.as_view(), name='pay'),
 ]
\ No newline at end of file
diff --git a/gms/squarepay/views.py b/gms/squarepay/views.py
index 959d6ea547e616e27faaa41c7515ad7b18fc1540..3ee1d09619105c68b464d8908009dc58d94cfa09 100644
--- a/gms/squarepay/views.py
+++ b/gms/squarepay/views.py
@@ -1,6 +1,6 @@
 import uuid
-from django.shortcuts import render
-from django.views.generic.base import TemplateView
+from django.views.generic.detail import DetailView
+from django.http import HttpResponse, HttpResponseRedirect, Http404
 from django.contrib import messages
 from django.conf import settings
 
@@ -9,26 +9,28 @@ from squareconnect.rest import ApiException
 from squareconnect.apis.transactions_api import TransactionsApi
 from squareconnect.apis.locations_api import LocationsApi
 
-class PaymentFormView(TemplateView):
+from .models import CardPayment
+
+class PaymentFormView(DetailView):
     """
     Handles the backend stuff for the Square payment form.
     See https://docs.connect.squareup.com/payments/sqpaymentform/setup
     """
 
     template_name = 'payment_form.html'
-    methods = ['get', 'post']
 
-    app_id = None
-    loc_id = None
-    access_key = None
-    amount = None
-    idempotency_key = None
-    sqapi = None
-    charge_response = None
+    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'
+    query_pk_and_slug = True
+    context_object_name = 'payment'
 
     def __init__(self, *args, **kwargs):
-        self.amount = kwargs.pop('payment_amount', 100)
         super().__init__(*args, **kwargs)
 
         # get things from settings
@@ -45,40 +47,36 @@ class PaymentFormView(TemplateView):
         context.update({
             'app_id': self.app_id,
             'loc_id': self.loc_id,
-            'response': self.charge_response,
         })
         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 passed or invalid card data.")
+            messages.error(request, "No nonce was generated! Please try reloading the page and submit again.")
             return self.get(request)
 
         api_inst = TransactionsApi(self.sqapi)
 
-        # this can be reused so we don't double charge the customer
-        if (self.idempotency_key is None): 
-            self.idempotency_key = str(uuid.uuid1())
-        
         body = {
             'idempotency_key': self.idempotency_key,
             'card_nonce': nonce,
             'amount_money': {
-                'amount': self.amount,
+                'amount': amount,
                 'currency': 'AUD'
             }
         }
 
         try:
             api_response = api_inst.charge(self.loc_id, body)
-            self.charge_response = api_response.transaction
+            messages.success(request, "Your payment of %1.2f was successful.", amount)
         except ApiException as e:
-            self.charge_response = None
             messages.error(request, "Exception while calling TransactionApi::charge: %s" % e)
         
-        return self.get(request)
-
+        # redirect to success URL
+        if (self.object.completed_url is None):
+            return self.get(request)
+        return HttpResponseRedirect(self.object.completed_url)