diff --git a/pip-packages.txt b/pip-packages.txt
index 445f377d76cfda7f5b2412d1f12c9871595e00c4..ce6254516f9f814849844a3708121e4a89006338 100644
--- a/pip-packages.txt
+++ b/pip-packages.txt
@@ -25,5 +25,6 @@ pytz==2019.1
 requests==2.28.2
 six==1.12.0
 sqlparse==0.3.0
+squareconnect==2.20190410.0
 squareup==26.0.0.20230419
-urllib3==1.25
+urllib3==1.26.15
diff --git a/src/squarepay/payments.py b/src/squarepay/payments.py
index b41885e6c80bd7bd356fd5c2dd961f0b8b038dcc..7c69beeb8e7d552b58cdb9dd0f7af13803ac9675 100644
--- a/src/squarepay/payments.py
+++ b/src/squarepay/payments.py
@@ -5,6 +5,9 @@ import logging
 
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
+from square.client import Client
+
+log = logging.getLogger('squarepay')
 
 # load the configuration values
 app_id = getattr(settings, 'SQUARE_APP_ID', None)
@@ -15,7 +18,9 @@ access_key = getattr(settings, 'SQUARE_ACCESS_TOKEN', None)
 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")
 
-def try_capture_payment(card_payment, nonce):
+client = Client(access_token=access_key, environment='sandbox')
+
+def try_capture_payment(card_payment, source_id, verification_token):
     """
     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
@@ -24,10 +29,26 @@ def try_capture_payment(card_payment, nonce):
     """
     request_body = {
         'idempotency_key': card_payment.idempotency_key,
-        'card_nonce': nonce,
+        'source_id': source_id,
+        'verification_token': verification_token,
         'amount_money': {
             'amount': card_payment.amount,
             'currency': 'AUD'
         }
     }
 
+    result = client.payments.create_payment(body=request_body)
+
+    if result.is_success():
+        card_payment.set_paid()
+        return {
+            "success": True,
+            "receipt_url": result.body["payment"]["receipt_url"],
+        }
+        # Call the error method to see if the call failed
+    elif result.is_error():
+        return {
+            "success": False,
+            "errors": result.errors,
+        }
+
diff --git a/src/squarepay/views.py b/src/squarepay/views.py
index c80825f531d6848bf3eff53cb27033cfbb58d9e6..fd8ae945a7dc4a21a2cde7e2818a6b75e16129a3 100644
--- a/src/squarepay/views.py
+++ b/src/squarepay/views.py
@@ -1,3 +1,4 @@
+import json
 import uuid
 from django.views.generic.base import RedirectView, View
 from django.views.generic.detail import DetailView
@@ -34,23 +35,32 @@ class PaymentFormMixin:
         payment.set_paid()
         messages.success(self.request, "Your payment of $%1.2f was successful." % (payment.amount / 100.0))
 
-    def payment_error(self, payment):
+    def payment_error(self, payment, error):
         messages.error(self.request, "Your payment of $%1.2f was unsuccessful. Please try again later." % (payment.amount / 100.0))
+        messages.error(self.request, error)
         payment.delete()
 
     def post(self, request, *args, **kwargs):
-        nonce = request.POST.get('nonce', None)
+        data = json.loads(request.body.decode('utf-8'))
+        source_id = data['sourceId']
+        verification_token = data['verificationToken']
+        
         card_payment = self.get_object()
         amount_aud = card_payment.amount / 100.0
-
-        if (nonce is None or nonce == ""):
+        
+        if (source_id is None or source_id == ""):
             messages.error(request, "Failed to collect card details. Please reload the page and submit again.")
             return self.get(request)
 
-        if try_capture_payment(card_payment, nonce):
+        payment = try_capture_payment(card_payment, source_id, verification_token)
+        if payment["success"]:
             self.payment_success(card_payment)
+            messages.success(request, "Receipt: %s" % payment["receipt_url"])
+            return HttpResponse(json.dumps(payment))
         else:
-            self.payment_error(card_payment)
+            error = payment['errors'][0]['detail']
+            self.payment_error(card_payment, error)
+            return HttpResponse(json.dumps(payment), status=403)
 
         # redirect to success URL, or redisplay the form with a success message if none is given
         return HttpResponseRedirect(self.get_completed_url())
@@ -110,7 +120,8 @@ class MembershipPaymentView(MemberAccessMixin, PaymentFormMixin, DetailView):
         if (self.object is None):
             # the membership is already marked as paid and no CardPayment exists
             # 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.")
+            if "/payment/" not in self.request.META['HTTP_REFERER']:
+                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)
diff --git a/src/static/squarepay.css b/src/static/squarepay.css
index 91776e920b9cb75bf8848afdb097b613b29b2c02..44080ce36be3a1d58897f12749acabc6f7a16d79 100644
--- a/src/static/squarepay.css
+++ b/src/static/squarepay.css
@@ -119,7 +119,6 @@ div#sq-expiration-date, div#sq-card-number, div#sq-cvv {
 
 .payment-info {
     float: left;
-    width: 380px;
     margin: 20px auto;
     box-sizing: content-box;
     border: 2px solid #eee;
@@ -134,73 +133,6 @@ div#sq-expiration-date, div#sq-card-number, div#sq-cvv {
     margin: 0;
 }
 
-/*#content, #container, body {
-    height: 100%;
-}
-*/
-/* Customize the Apple Pay on the Web button */
-#sq-apple-pay {
-    width: 100%;
-    height: 48px;
-    padding: 0;
-    margin: 24px 0 16px 0;
-    background-image: -webkit-named-image(apple-pay-logo-white);
-    background-color: black;
-    background-size: 100% 60%;
-    background-repeat: no-repeat;
-    background-position: 50% 50%;
-    border-radius: 4px;
-    cursor: pointer;
-    display: none;
-}
-
-/* Customize the Masterpass button */
-#sq-masterpass {
-    width: 100%;
-    height: 48px;
-    padding: 0;
-    margin: 24px 0 24px;
-    background-image: url("https://masterpass.com/dyn/img/acc/global/mp_mark_hor_wht.svg");
-    background-color: black;
-    background-size: 60% 60%;
-    background-repeat: no-repeat;
-    background-position: calc((100% - 32px) / 2) 50%;
-    border-radius: 4px;
-    cursor: pointer;
-    display: none;
-}
-
-/*#sq-masterpass::after {
-    box-sizing: border-box;
-    float: right;
-    width: 32px;
-    height: 48px;
-    padding-top: 12px;
-    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 {
-    min-width: 200px;
-    min-height: 40px;
-    padding: 11px 24px;
-    margin: 10px;
-    background-color: #000;
-    background-image: url(data:image/svg+xml,%3Csvg%20width%3D%22103%22%20height%3D%2217%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M.148%202.976h3.766c.532%200%201.024.117%201.477.35.453.233.814.555%201.085.966.27.41.406.863.406%201.358%200%20.495-.124.924-.371%201.288s-.572.64-.973.826v.084c.504.177.912.471%201.225.882.313.41.469.891.469%201.442a2.6%202.6%200%200%201-.427%201.47c-.285.43-.667.763-1.148%201.001A3.5%203.5%200%200%201%204.082%2013H.148V2.976zm3.696%204.2c.448%200%20.81-.14%201.085-.42.275-.28.413-.602.413-.966s-.133-.684-.399-.959c-.266-.275-.614-.413-1.043-.413H1.716v2.758h2.128zm.238%204.368c.476%200%20.856-.15%201.141-.448.285-.299.427-.644.427-1.036%200-.401-.147-.749-.441-1.043-.294-.294-.688-.441-1.183-.441h-2.31v2.968h2.366zm5.379.903c-.453-.518-.679-1.239-.679-2.163V5.86h1.54v4.214c0%20.579.138%201.013.413%201.302.275.29.637.434%201.085.434.364%200%20.686-.096.966-.287.28-.191.495-.446.644-.763a2.37%202.37%200%200%200%20.224-1.022V5.86h1.54V13h-1.456v-.924h-.084c-.196.336-.5.611-.91.826-.41.215-.845.322-1.302.322-.868%200-1.528-.259-1.981-.777zm9.859.161L16.352%205.86h1.722l2.016%204.858h.056l1.96-4.858H23.8l-4.41%2010.164h-1.624l1.554-3.416zm8.266-6.748h1.666l1.442%205.11h.056l1.61-5.11h1.582l1.596%205.11h.056l1.442-5.11h1.638L36.392%2013h-1.624L33.13%207.876h-.042L31.464%2013h-1.596l-2.282-7.14zm12.379-1.337a1%201%200%200%201-.301-.735%201%201%200%200%201%20.301-.735%201%201%200%200%201%20.735-.301%201%201%200%200%201%20.735.301%201%201%200%200%201%20.301.735%201%201%200%200%201-.301.735%201%201%200%200%201-.735.301%201%201%200%200%201-.735-.301zM39.93%205.86h1.54V13h-1.54V5.86zm5.568%207.098a1.967%201.967%200%200%201-.686-.406c-.401-.401-.602-.947-.602-1.638V7.218h-1.246V5.86h1.246V3.844h1.54V5.86h1.736v1.358H45.75v3.36c0%20.383.075.653.224.812.14.187.383.28.728.28.159%200%20.299-.021.42-.063.121-.042.252-.11.392-.203v1.498c-.308.14-.681.21-1.12.21-.317%200-.616-.051-.896-.154zm3.678-9.982h1.54v2.73l-.07%201.092h.07c.205-.336.511-.614.917-.833.406-.22.842-.329%201.309-.329.868%200%201.53.254%201.988.763.457.509.686%201.202.686%202.079V13h-1.54V8.688c0-.541-.142-.947-.427-1.218-.285-.27-.656-.406-1.113-.406-.345%200-.656.098-.931.294a2.042%202.042%200%200%200-.651.777%202.297%202.297%200%200%200-.238%201.029V13h-1.54V2.976zm32.35-.341v4.083h2.518c.6%200%201.096-.202%201.488-.605.403-.402.605-.882.605-1.437%200-.544-.202-1.018-.605-1.422-.392-.413-.888-.62-1.488-.62h-2.518zm0%205.52v4.736h-1.504V1.198h3.99c1.013%200%201.873.337%202.582%201.012.72.675%201.08%201.497%201.08%202.466%200%20.991-.36%201.819-1.08%202.482-.697.665-1.559.996-2.583.996h-2.485v.001zm7.668%202.287c0%20.392.166.718.499.98.332.26.722.391%201.168.391.633%200%201.196-.234%201.692-.701.497-.469.744-1.019.744-1.65-.469-.37-1.123-.555-1.962-.555-.61%200-1.12.148-1.528.442-.409.294-.613.657-.613%201.093m1.946-5.815c1.112%200%201.989.297%202.633.89.642.594.964%201.408.964%202.442v4.932h-1.439v-1.11h-.065c-.622.914-1.45%201.372-2.486%201.372-.882%200-1.621-.262-2.215-.784-.594-.523-.891-1.176-.891-1.96%200-.828.313-1.486.94-1.976s1.463-.735%202.51-.735c.892%200%201.629.163%202.206.49v-.344c0-.522-.207-.966-.621-1.33a2.132%202.132%200%200%200-1.455-.547c-.84%200-1.504.353-1.995%201.062l-1.324-.834c.73-1.045%201.81-1.568%203.238-1.568m11.853.262l-5.02%2011.53H96.42l1.864-4.034-3.302-7.496h1.635l2.387%205.749h.032l2.322-5.75z%22%20fill%3D%22%23FFF%22%2F%3E%3Cpath%20d%3D%22M75.448%207.134c0-.473-.04-.93-.116-1.366h-6.344v2.588h3.634a3.11%203.11%200%200%201-1.344%202.042v1.68h2.169c1.27-1.17%202.001-2.9%202.001-4.944%22%20fill%3D%22%234285F4%22%2F%3E%3Cpath%20d%3D%22M68.988%2013.7c1.816%200%203.344-.595%204.459-1.621l-2.169-1.681c-.603.406-1.38.643-2.29.643-1.754%200-3.244-1.182-3.776-2.774h-2.234v1.731a6.728%206.728%200%200%200%206.01%203.703%22%20fill%3D%22%2334A853%22%2F%3E%3Cpath%20d%3D%22M65.212%208.267a4.034%204.034%200%200%201%200-2.572V3.964h-2.234a6.678%206.678%200%200%200-.717%203.017c0%201.085.26%202.11.717%203.017l2.234-1.731z%22%20fill%3D%22%23FABB05%22%2F%3E%3Cpath%20d%3D%22M68.988%202.921c.992%200%201.88.34%202.58%201.008v.001l1.92-1.918c-1.165-1.084-2.685-1.75-4.5-1.75a6.728%206.728%200%200%200-6.01%203.702l2.234%201.731c.532-1.592%202.022-2.774%203.776-2.774%22%20fill%3D%22%23E94235%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E);
-    background-origin: content-box;
-    background-position: center;
-    background-repeat: no-repeat;
-    background-size: contain;
-    border: 0;
-    border-radius: 4px;
-    box-shadow: 0 1px 1px 0 rgba(60, 64, 67, 0.30), 0 1px 3px 1px rgba(60, 64, 67, 0.15);
-    outline: 0;
-    cursor: pointer;
-    display: none;
-}
-
 #error {
     width: 100%;
     margin-top: 16px;
@@ -211,3 +143,191 @@ div#sq-expiration-date, div#sq-card-number, div#sq-cvv {
     opacity: 0.8;
     display: none;
 }
+
+#payment-form {
+    max-width: 550px;
+    min-width: 300px;
+  }
+  
+  .buyer-inputs {
+    display: flex;
+    gap: 20px;
+    justify-content: space-between;
+    border: none;
+    margin: 0;
+    padding: 0;
+  }
+  
+  #card-container {
+    /* this height depends on the size of the container element */
+    /* We transition from a single row to double row at 485px */
+    /* Settting this min-height minimizes the impact of the card form loading */
+    min-height: 90px;
+  }
+  
+  #gift-card-container {
+    margin-top: 45px;
+    min-height: 90px;
+  }
+  
+  @media screen and (max-width: 500px) {
+    #card-container {
+      min-height: 140px;
+    }
+  }
+  
+  #ach-button {
+    margin-top: 20px;
+  }
+  
+  #landing-page-layout {
+    width: 80%;
+    margin: 150px auto;
+    max-width: 1000px;
+  }
+  
+  #its-working {
+    color: #737373;
+  }
+  
+  #example-container {
+    width: 100%;
+    border: 1px solid #b3b3b3;
+    padding: 48px;
+    margin: 32px 0;
+    border-radius: 12px;
+  }
+  
+  #example-list {
+    display: flex;
+    flex-direction: column;
+    gap: 15px;
+  }
+  
+  #customer-input {
+    margin-bottom: 40px;
+  }
+  
+  #card-input {
+    margin-top: 0;
+    margin-bottom: 40px;
+  }
+  
+  h3 {
+    margin: 0;
+  }
+  
+  p {
+    line-height: 24px;
+  }
+  
+  label {
+    font-size: 12px;
+    width: 100%;
+  }
+  
+  input {
+    padding: 12px;
+    width: 100%;
+    border-radius: 5px;
+    border-width: 1px;
+    margin-top: 20px;
+    font-size: 16px;
+    border: 1px solid rgba(0, 0, 0, 0.15);
+  }
+  
+  input:focus {
+    border: 1px solid #006aff;
+  }
+  
+  button {
+    color: #ffffff;
+    background-color: #006aff;
+    border-radius: 5px;
+    cursor: pointer;
+    border-style: none;
+    user-select: none;
+    outline: none;
+    font-size: 16px;
+    font-weight: 500;
+    line-height: 24px;
+    padding: 12px;
+    width: 100%;
+    box-shadow: 1px;
+  }
+  
+  button:active {
+    background-color: rgb(0, 85, 204);
+  }
+  
+  button:disabled {
+    background-color: rgba(0, 0, 0, 0.05);
+    color: rgba(0, 0, 0, 0.3);
+  }
+
+  button.success {
+    color: #FFFFFF;
+    background-color:rgb(82, 165, 0)
+  }
+
+  button.failure {
+    color: #FFFFFF;
+    background-color: #E02F2F;
+  }
+
+  .lds-ellipsis {
+    display: inline-block;
+    position: relative;
+    width: 80px;
+    height: 80px;
+  }
+  .lds-ellipsis div {
+    position: absolute;
+    top: 33px;
+    width: 13px;
+    height: 13px;
+    border-radius: 50%;
+    background: #3e3e3e;
+    animation-timing-function: cubic-bezier(0, 1, 1, 0);
+  }
+  .lds-ellipsis div:nth-child(1) {
+    left: 8px;
+    animation: lds-ellipsis1 0.6s infinite;
+  }
+  .lds-ellipsis div:nth-child(2) {
+    left: 8px;
+    animation: lds-ellipsis2 0.6s infinite;
+  }
+  .lds-ellipsis div:nth-child(3) {
+    left: 32px;
+    animation: lds-ellipsis2 0.6s infinite;
+  }
+  .lds-ellipsis div:nth-child(4) {
+    left: 56px;
+    animation: lds-ellipsis3 0.6s infinite;
+  }
+  @keyframes lds-ellipsis1 {
+    0% {
+      transform: scale(0);
+    }
+    100% {
+      transform: scale(1);
+    }
+  }
+  @keyframes lds-ellipsis3 {
+    0% {
+      transform: scale(1);
+    }
+    100% {
+      transform: scale(0);
+    }
+  }
+  @keyframes lds-ellipsis2 {
+    0% {
+      transform: translate(0, 0);
+    }
+    100% {
+      transform: translate(24px, 0);
+    }
+  }
+  
\ No newline at end of file
diff --git a/src/templates/payment_form.html b/src/templates/payment_form.html
index 90aa360d3d9760fb15accfcad78db44869be6054..489042ed5252817240159304f3c238d78e70ce3b 100644
--- a/src/templates/payment_form.html
+++ b/src/templates/payment_form.html
@@ -5,14 +5,165 @@
 
 {% block extrahead %}
 
+<script type="text/javascript" src="web.squarecdn.com/v1/square.js"></script>
 {# bring the location IDs into javascript so the next bits know about them #}
 <script type="text/javascript"> 
-var applicationId = "{{ app_id }}";
+
+var appId = "{{ app_id }}";
 var locationId = "{{ loc_id }}";
 var amount = "{{ payment.amount }}";
+
+async function initializeCard(payments) {
+    const card = await payments.card();
+    await card.attach('#card-container');
+    return card;
+}
+
+// Call this function to send a payment token, buyer name, and other details
+// to the project server code so that a payment can be created with 
+// Payments API
+async function createPayment(token, verificationToken) {
+    const csrfToken = document.querySelector('input[name=csrfmiddlewaretoken]').value;
+    const body = JSON.stringify({
+        locationId,
+        sourceId: token,
+        verificationToken: verificationToken,
+    });
+
+    //   if (verificationToken !== undefined) {
+    //    body.verificationToken = verificationToken;
+    //  }
+
+    const paymentResponse = await fetch(location.href, {
+        method: 'POST',
+        headers: {
+            'Content-Type': 'application/json',
+            'X-CSRFToken': csrfToken,
+        },
+        body,
+        credentials: 'same-origin',
+    });
+
+    return paymentResponse.json();
+}
+
+// This function tokenizes a payment method. 
+// The ‘error’ thrown from this async function denotes a failed tokenization,
+// which is due to buyer error (such as an expired card). It is up to the
+// developer to handle the error and provide the buyer the chance to fix
+// their mistakes.
+async function tokenize(paymentMethod) {
+    const tokenResult = await paymentMethod.tokenize();
+    if (tokenResult.status === 'OK') {
+        return tokenResult.token;
+    } else {
+        let errorMessage = `Tokenization failed-status: ${tokenResult.status}`;
+        if (tokenResult.errors) {
+            errorMessage += ` and errors: ${JSON.stringify(
+          tokenResult.errors
+        )}`;
+        }
+        throw new Error(errorMessage);
+    }
+}
+
+async function verifyBuyer(payments, token) {
+    const verificationDetails = {
+        amount: '{{ payment.amount }}',
+        /* collected from the buyer */
+        billingContact: {},
+        currencyCode: 'AUD',
+        intent: 'CHARGE',
+    };
+
+    const verificationResults = await payments.verifyBuyer(
+        token,
+        verificationDetails
+    );
+
+    return verificationResults.token;
+}
+
+// Helper method for displaying the Payment Status on the screen.
+// status is either SUCCESS or FAILURE;
+function displayPaymentResults(status) {
+    const cardButton = document.getElementById(
+        'card-button'
+    );
+    if (status === 'SUCCESS') {
+        cardButton.classList.remove('failure');
+        cardButton.classList.add('success');
+        cardButton.innerText = 'Payment successful! Redirecting...';
+    } else {
+        cardButton.classList.remove('success');
+        cardButton.classList.add('failure');
+        cardButton.innerText = 'Payment failed :( Refreshing...';
+    }
+}
+
+
+document.addEventListener('DOMContentLoaded', async function() {
+    const loader = document.getElementById('spinner');
+    const cardButton = document.getElementById(
+        'card-button'
+    );
+
+    cardButton.disabled = true;
+    if (!window.Square) {
+        throw new Error('Square.js failed to load properly');
+    }
+    const payments = window.Square.payments(appId, locationId);
+    let card;
+    try {
+        card = await initializeCard(payments);
+        loader.style.display = 'none';
+        cardButton.disabled = false;
+    } catch (e) {
+        console.error('Initializing Card failed', e);
+        return;
+    }
+
+    async function handlePaymentMethodSubmission(event, paymentMethod, shouldVerify = true) {
+        event.preventDefault();
+        cardButton.disabled = true;
+        cardButton.innerText = "Processing...";
+        const token = await tokenize(paymentMethod);
+
+        let verificationToken;
+        if (shouldVerify) {
+            verificationToken = await verifyBuyer(
+                payments,
+                token
+            );
+        }
+
+        const paymentResults = await createPayment(token, verificationToken);
+        if (paymentResults.success) {
+            displayPaymentResults('SUCCESS');
+            setTimeout(
+                function(){window.location.href = "{{ payment.completed_url }}";}, 3000
+            );
+        } else {
+            cardButton.disabled = false;
+            displayPaymentResults('FAILURE');
+            console.error(paymentResults.errors);
+            setTimeout(
+                function(){window.location.href = "{{ payment.completed_url }}";}, 3000
+            );
+        }
+        console.debug('Payment Success', paymentResults);
+
+    }
+
+    cardButton.addEventListener('click', async function(event) {
+        await handlePaymentMethodSubmission(event, card, true);
+    });
+
+});
 </script>
 
 <link rel="stylesheet" type="text/css" href="{% static 'squarepay.css' %}">
+
 {% endblock %}
 
 
@@ -21,15 +172,17 @@ var amount = "{{ payment.amount }}";
 <div class="form-container text-center">
     <div class="form-header">
         <span class="tips {% if payment.is_paid %}error{% endif %}">{% block tips %}
-        {% if payment.is_paid %}
-            <p><b>It appears you have already successfully attempted this payment.</b>
-            {% if payment.completed_url %}
-                <br><br>Perhaps you were meaning to find <a href="{{ payment.completed_url }}">this page</a>.
-            {% endif %}</p>
-        {% else %}
-            <b><i>Please fill in your card details below.</i></b>
-        {% endif %}
-        {% endblock %}</span>
+            {% if payment.is_paid %}
+                <p><b>It appears you have already successfully attempted this payment.</b>
+                    {% if payment.completed_url %}
+                        <br><br>Perhaps you were meaning to find <a href="{{ payment.completed_url }}">this page</a>.
+                    {% endif %}
+                </p>
+            {% else %}
+                <b><i>Please fill in your card details below.</i></b>
+            {% endif %}
+        </span>
+{% endblock %}
 
         <noscript>
             <span class="tips error">Please enable javascript to use the payment form.</span>
@@ -48,6 +201,14 @@ var amount = "{{ payment.amount }}";
         </div>
         <!-- the element #form-container is used to create the Square payment form (hardcoded) -->
         <div id="form-container" class="payment-info">
+            <div class="float-container">
+                {% csrf_token %}
+                <div id=spinner class="lds-ellipsis" id="payment-form"><div></div><div></div><div></div><div></div></div>
+                <form id="payment-form">
+                    <div id="card-container"></div>
+                    <button id="card-button" type="button">Pay {{ amount }}</button>
+                </form>
+            </div> <!-- end #float-container -->
         </div> <!-- end #form-container -->
     </div>
 </div>