diff --git a/gms/gms/settings.py b/gms/gms/settings.py
index 4c34c04ea0f446a64e487601c9e15a40f1b2123e..e620d088986454e171b94d64bff9232c4ddfcda1 100644
--- a/gms/gms/settings.py
+++ b/gms/gms/settings.py
@@ -14,6 +14,7 @@ from gms.settings_local import *
 # Application definition
 
 INSTALLED_APPS = (
+    'sslserver',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
@@ -22,6 +23,7 @@ INSTALLED_APPS = (
     'django.contrib.staticfiles',
     'memberdb',
     'import_members',
+    'squarepay',
 )
 
 MIDDLEWARE = [
diff --git a/gms/gms/settings_local.example.py b/gms/gms/settings_local.example.py
index f8984ca920cd8bfd05cc5fb8c02477bfb1b333a9..08919b4c64caaed4fb86cea24b51777bfeac1ad6 100644
--- a/gms/gms/settings_local.example.py
+++ b/gms/gms/settings_local.example.py
@@ -84,4 +84,9 @@ AUTH_LDAP_USER_FLAGS_BY_GROUP = {
 
     # superusers have all permissions (but also need staff to login to admin site)
     "is_superuser": ADMIN_ACCESS_QUERY,
-}
\ No newline at end of file
+}
+
+# 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
diff --git a/gms/gms/urls.py b/gms/gms/urls.py
index 1a35ea5e625c0b1d05709d3f48a6cc920405f489..1252591997000e343e058180c6ccf32587bdd9e1 100644
--- a/gms/gms/urls.py
+++ b/gms/gms/urls.py
@@ -5,4 +5,5 @@ from . import admin
 urlpatterns = [
     path('', include('memberdb.urls')),
     path('admin/', admin.site.urls),
+    path('payment/', include('squarepay.urls')),
 ]
diff --git a/gms/memberdb/static/main.css b/gms/memberdb/static/main.css
index 7c4859e4ca3861caacdb97eb362594171cba56aa..306edcd1620aea08033817def251394b615aa6b3 100644
--- a/gms/memberdb/static/main.css
+++ b/gms/memberdb/static/main.css
@@ -26,7 +26,7 @@ body {
     transform: translate(-50%, -50%);
 }
 
-.form-container {
+.form-container, #form-container {
     background-color: #ffe;
     border: 2px solid #8fc;
     border-radius: 5px;
diff --git a/gms/squarepay/__init__.py b/gms/squarepay/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7f1520ed64128702e574269b609c4f258e954b1
--- /dev/null
+++ b/gms/squarepay/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'squarepay.apps.SquarePayConfig'
\ No newline at end of file
diff --git a/gms/squarepay/admin.py b/gms/squarepay/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/gms/squarepay/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/gms/squarepay/apps.py b/gms/squarepay/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a6550339c2c805b2bd7638d72f39efa1b547989
--- /dev/null
+++ b/gms/squarepay/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class SquarePayConfig(AppConfig):
+    name = 'squarepay'
+    verbose_name = 'Square Payments'
diff --git a/gms/squarepay/migrations/__init__.py b/gms/squarepay/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gms/squarepay/models.py b/gms/squarepay/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91
--- /dev/null
+++ b/gms/squarepay/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/gms/squarepay/static/sqpaymentform-basic.css b/gms/squarepay/static/sqpaymentform-basic.css
new file mode 100644
index 0000000000000000000000000000000000000000..4bd6a854862a6b6237c07d384044e9491a2b723d
--- /dev/null
+++ b/gms/squarepay/static/sqpaymentform-basic.css
@@ -0,0 +1,222 @@
+* {
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+body, html {
+  color: #373F4A;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-weight: normal;
+}
+
+iframe {
+  margin: 0;
+  padding: 0;
+  border: 0;
+}
+
+button {
+  border: 0;
+}
+
+hr {
+  height: 1px;
+  border: 0;
+  background-color: #CCC;
+}
+
+fieldset {
+  margin: 0;
+  padding: 0;
+  border: 0;
+}
+
+
+/*#form-container {
+  position: relative;
+  width: 380px;
+  margin: 0 auto;
+  top: 50%;
+  transform: translateY(-50%);
+}*/
+
+.label {
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 24px;
+  letter-spacing: 0.5;
+  text-transform: uppercase;
+}
+
+.third {
+  float: left;
+  width: calc((100% - 32px) / 3);
+  padding: 0;
+  margin: 0 16px 16px 0;
+}
+
+.third:last-of-type {
+  margin-right: 0;
+}
+
+/* Define how SqPaymentForm iframes should look */
+.sq-input {
+  box-sizing: border-box;
+  border: 1px solid #E0E2E3;
+  border-radius: 4px;
+  outline-offset: -2px;
+  display: inline-block;
+  -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;
+}
+
+/* Define how SqPaymentForm iframes should look when they have focus */
+.sq-input--focus {
+  border: 1px solid #4A90E2;
+  background-color: rgba(74,144,226,0.02);
+}
+
+
+/* Define how SqPaymentForm iframes should look when they contain invalid values */
+.sq-input--error {
+  border: 1px solid #E02F2F;
+  background-color: rgba(244,47,47,0.02);
+}
+
+#sq-card-number {
+  margin-bottom: 16px;
+}
+
+/* Customize the "Pay with Credit Card" button */
+.button-credit-card {
+  width: 100%;
+  height: 56px;
+  margin-top: 10px;
+  background: #4A90E2;
+  border-radius: 4px;
+  cursor: pointer;
+  display: block;
+  color: #FFFFFF;
+  font-size: 16px;
+  line-height: 24px;
+  font-weight: 700;
+  letter-spacing: 0;
+  text-align: center;
+  -webkit-transition: background .2s ease-in-out;
+     -moz-transition: background .2s ease-in-out;
+      -ms-transition: background .2s ease-in-out;
+          transition: background .2s ease-in-out;
+}
+
+.button-credit-card:hover {
+  background-color: #4281CB;
+}
+
+
+/* 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: 100% 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;
+}
+
+
+#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;
+  font-size: 14px;
+  color: red;
+  font-weight: 500;
+  text-align: center;
+  opacity: 0.8;
+}
diff --git a/gms/squarepay/static/sqpaymentform-basic.js b/gms/squarepay/static/sqpaymentform-basic.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b264ce5760d9d8a7a33c783584db1c030019fd7
--- /dev/null
+++ b/gms/squarepay/static/sqpaymentform-basic.js
@@ -0,0 +1,207 @@
+/** Javascript to handle the Square payments form.
+ * Adapted from https://docs.connect.squareup.com/payments/sqpaymentform/setup */
+
+/*
+ * function: requestCardNonce
+ *
+ * requestCardNonce is triggered when the "Pay with credit card" button is
+ * clicked
+ *
+ * Modifying this function is not required, but can be customized if you
+ * wish to take additional action when the form button is clicked.
+ */
+function requestCardNonce(event) {
+
+    // Don't submit the form until SqPaymentForm returns with a nonce
+    event.preventDefault();
+
+    // Request a nonce from the SqPaymentForm object
+    paymentForm.requestCardNonce();
+}
+
+// Create and initialize a payment form object
+var paymentForm = new SqPaymentForm({
+
+    // Initialize the payment form elements
+    applicationId: applicationId,
+    locationId: locationId,
+    inputClass: 'sq-input',
+    autoBuild: false,
+
+    // Customize the CSS for SqPaymentForm iframe elements
+    inputStyles: [{
+        fontSize: '16px',
+        fontFamily: 'Helvetica Neue',
+        padding: '16px',
+        color: '#373F4A',
+        backgroundColor: 'transparent',
+        lineHeight: '24px',
+        placeholderColor: '#CCC',
+        _webkitFontSmoothing: 'antialiased',
+        _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'
+    },
+
+    // Initialize the credit card placeholders
+    cardNumber: {
+        elementId: 'sq-card-number',
+        placeholder: '• • • •    • • • •    • • • •    • • • •'
+    },
+    cvv: {
+        elementId: 'sq-cvv',
+        placeholder: 'CVV'
+    },
+    expirationDate: {
+        elementId: 'sq-expiration-date',
+        placeholder: 'MM/YY'
+    },
+    postalCode: {
+        elementId: 'sq-postal-code',
+        placeholder: '6009'
+    },
+
+    // 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 () {
+
+            return {
+                requestShippingAddress: false,
+                requestBillingInfo: false,
+                currencyCode: "AUD",
+                countryCode: "AU",
+                total: {
+                    label: "University Computer Club Inc.",
+                    amount: "100",
+                    pending: false
+                },
+                lineItems: [
+                    {
+                        label: "Subtotal",
+                        amount: "100",
+                        pending: false
+                    }
+                ]
+            }
+        },
+
+        /* 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:");
+                errors.forEach(function (error) {
+                    console.log('    ' + error.message);
+                    alert(error.message);
+                });
+
+                return;
+            }
+            // Assign the nonce value to the hidden form field
+            document.getElementById('card-nonce').value = nonce;
+
+            // POST the nonce form to the payment processing page
+            document.getElementById('nonce-form').submit();
+
+        },
+
+        /* callback function: unsupportedBrowserDetected
+         * Triggered when: the page loads and an unsupported browser is detected */
+        unsupportedBrowserDetected: function () {
+            /* PROVIDE FEEDBACK TO SITE VISITORS */
+        },
+
+        /* callback function: inputEventReceived
+         * Triggered when: visitors interact with SqPaymentForm iframe elements. */
+        inputEventReceived: function (inputEvent) {
+            switch (inputEvent.eventType) {
+                case 'focusClassAdded':
+                    /* HANDLE AS DESIRED */
+                    break;
+                case 'focusClassRemoved':
+                    /* HANDLE AS DESIRED */
+                    break;
+                case 'errorClassAdded':
+                    document.getElementById("error").innerHTML = "Please fix card information errors before continuing.";
+                    break;
+                case 'errorClassRemoved':
+                    /* HANDLE AS DESIRED */
+                    document.getElementById("error").style.display = "none";
+                    break;
+                case 'cardBrandChanged':
+                    /* HANDLE AS DESIRED */
+                    break;
+                case 'postalCodeChanged':
+                    /* HANDLE AS DESIRED */
+                    break;
+            }
+        },
+
+        /* callback function: paymentFormLoaded
+         * Triggered when: SqPaymentForm is fully loaded */
+        paymentFormLoaded: function () {
+            /* HANDLE AS DESIRED */
+            console.log("The form loaded!");
+        }
+    }
+});
+
+document.addEventListener("DOMContentLoaded", function(event) {
+    if (SqPaymentForm.isSupportedBrowser()) {
+        paymentForm.build();
+        paymentForm.recalculateSize();
+    }
+});
\ No newline at end of file
diff --git a/gms/squarepay/templates/payment_form.html b/gms/squarepay/templates/payment_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..37d58066f03cbd0ae65ff6e69be8237a3e79db6d
--- /dev/null
+++ b/gms/squarepay/templates/payment_form.html
@@ -0,0 +1,81 @@
+{% extends "base.html" %}
+{% load static %}
+
+{% block extrahead %}
+<!-- link to the SqPaymentForm library -->
+<script type="text/javascript" src="https://js.squareup.com/v2/paymentform"></script>
+
+<script type="text/javascript">
+/* {# bring the location IDs into javascript so the next bits know about them #} */
+var applicationId = "{{ app_id }}";
+var locationId = "{{ loc_id }}";
+</script>
+
+<!-- link to the local SqPaymentForm initialization -->
+<script type="text/javascript" src="{% static 'sqpaymentform-basic.js' %}"></script>
+
+<!-- link to the custom styles for SqPaymentForm -->
+<link rel="stylesheet" type="text/css" href="{% static 'sqpaymentform-basic.css' %}">
+{% endblock %}
+
+{% block content %}
+{% if response %}
+<h1>{{ response }}</h1>
+{% endif %}
+
+<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>
+        <span class="label">Card Number</span>
+        <div id="sq-card-number"></div>
+
+        <div class="third">
+        <span class="label">Expiration</span>
+        <div id="sq-expiration-date"></div>
+        </div>
+
+        <div class="third">
+        <span class="label">CVV</span>
+        <div id="sq-cvv"></div>
+        </div>
+
+        <div class="third">
+        <span class="label">Postal</span>
+        <div id="sq-postal-code"></div>
+        </div>
+    </fieldset>
+
+    <button id="sq-creditcard" class="button-credit-card" onclick="requestCardNonce(event)">Pay $1.00</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 -->
+</div> <!-- end #form-container -->
+{% endblock %}
\ No newline at end of file
diff --git a/gms/squarepay/tests.py b/gms/squarepay/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/gms/squarepay/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/gms/squarepay/urls.py b/gms/squarepay/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..59c1b711f2de2402322302532cdfab7fc20ad612
--- /dev/null
+++ b/gms/squarepay/urls.py
@@ -0,0 +1,8 @@
+from django.urls import path
+
+from .views import PaymentFormView
+
+app_name = 'squarepay'
+urlpatterns = [
+    path('pay/', PaymentFormView.as_view(), name='pay'),
+]
\ No newline at end of file
diff --git a/gms/squarepay/views.py b/gms/squarepay/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..959d6ea547e616e27faaa41c7515ad7b18fc1540
--- /dev/null
+++ b/gms/squarepay/views.py
@@ -0,0 +1,85 @@
+import uuid
+from django.shortcuts import render
+from django.views.generic.base import TemplateView
+from django.contrib import messages
+from django.conf import settings
+
+import squareconnect
+from squareconnect.rest import ApiException
+from squareconnect.apis.transactions_api import TransactionsApi
+from squareconnect.apis.locations_api import LocationsApi
+
+class PaymentFormView(TemplateView):
+    """
+    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
+
+
+    def __init__(self, *args, **kwargs):
+        self.amount = kwargs.pop('payment_amount', 100)
+        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)
+        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.")
+            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,
+                'currency': 'AUD'
+            }
+        }
+
+        try:
+            api_response = api_inst.charge(self.loc_id, body)
+            self.charge_response = api_response.transaction
+        except ApiException as e:
+            self.charge_response = None
+            messages.error(request, "Exception while calling TransactionApi::charge: %s" % e)
+        
+        return self.get(request)
+
+
+
+        
+