Commit 89158c71 authored by frekk's avatar frekk
Browse files

started work on square payments app

parent 1b4b1905
......@@ -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 = [
......
......@@ -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
......@@ -5,4 +5,5 @@ from . import admin
urlpatterns = [
path('', include('memberdb.urls')),
path('admin/', admin.site.urls),
path('payment/', include('squarepay.urls')),
]
......@@ -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;
......
default_app_config = 'squarepay.apps.SquarePayConfig'
\ No newline at end of file
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class SquarePayConfig(AppConfig):
name = 'squarepay'
verbose_name = 'Square Payments'
from django.db import models
# Create your models here.
* {
-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;
}
/** 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
{% 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
from django.test import TestCase
# Create your tests here.
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
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