Commit 928e0e1c authored by frekk's avatar frekk
Browse files

added member payment metadata table

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