diff --git a/gms/squarepay/admin.py b/gms/squarepay/admin.py index 7f8f4f2ddd5254b6744ffe1e1c5bf99151003a29..a69a0ddffe437bf588b1e707b483444e0db05262 100644 --- a/gms/squarepay/admin.py +++ b/gms/squarepay/admin.py @@ -1,7 +1,7 @@ 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 -admin.site.register(CardPayment, CardPaymentAdmin) \ No newline at end of file +class MembershipPaymentAdmin(CardPaymentAdmin): + list_display = ['amount', 'url_field', 'date_created', 'is_paid', 'membership'] + +admin.site.register(CardPayment, CardPaymentAdmin) +admin.site.register(MembershipPayment, MembershipPaymentAdmin) diff --git a/gms/squarepay/models.py b/gms/squarepay/models.py index 7000231201cd48f1e9dda1d868064f2fc37742a5..4a3a420259e5b184fe222ac80973322ac3f43193 100644 --- a/gms/squarepay/models.py +++ b/gms/squarepay/models.py @@ -1,24 +1,25 @@ import uuid -from django.core.management.utils import get_random_secret_key + +from django.core.management.utils 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': self.pk, 'token': self.token }) \ No newline at end of file + return reverse('squarepay:pay', kwargs={ 'pk': self.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') diff --git a/gms/squarepay/urls.py b/gms/squarepay/urls.py index b31bd3166a46e8834b99639215daccc48ec19f2b..607cfed6e56898cc60d24452cb3688480f0eeac5 100644 --- a/gms/squarepay/urls.py +++ b/gms/squarepay/urls.py @@ -1,8 +1,9 @@ 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'), +] diff --git a/gms/squarepay/views.py b/gms/squarepay/views.py index 3ee1d09619105c68b464d8908009dc58d94cfa09..467aae1cde4dd31edde1073595d66ef41a9d05d2 100644 --- a/gms/squarepay/views.py +++ b/gms/squarepay/views.py @@ -1,15 +1,15 @@ 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 squareconnect.rest 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) context.update({ - '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): + set_paid(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 - try: - 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): + payment_success(card_payment) + messages.success(request, "Your payment of $%1.2f was successful." % amount_aud) + else: + 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()) + else: + return super().dispatch(request, *args, **kwargs) + + def payment_success(self, payment): + ms = payment.membership + ms.date_paid = timezone.now() + ms.payment_method = 'online' + ms.save() + super().payment_success(payment) + + def get_completed_url(self): + return reverse('memberdb:home')