PKJ)J9plugs_payments/views.py""" Payments Views """ from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response from rest_framework import permissions from plugs_payments import models @api_view(['GET']) @permission_classes([permissions.AllowAny]) def confirmation(request): """ Callback to be used by the payments platform """ reason = models.IfThenPayment.objects.confirmation(request.GET) return Response(data=reason) PKJ)Jɕ""plugs_payments/fields.py""" Plugs Payments Fields """ from django.db import models from plugs_core.validators import NumberOfDigitsValidator class ReferenceField(models.PositiveIntegerField): """ Custom model field that represent a payment reference """ default_validators = [NumberOfDigitsValidator(9)] description = "A payment reference" class EntityField(models.PositiveIntegerField): """ Custom model field that represents an entity """ default_validators = [NumberOfDigitsValidator(5)] description = "A payment entity" PKJ)J4plugs_payments/signals.py""" Plugs Payments Signals """ from django.dispatch import Signal # sent when a validated ifthen payment received valid_ifthen_payment_received = Signal() # sent when an invalid payment received # could be an error with a reference, value or entity # cannot be the anti phishing key invalid_ifthen_payment_received = Signal() # sent when a request to the confirmation callback # was made with an incorrect or missing anti phisphing key suspicious_ifthen_payment_received = Signal() PK)J?llplugs_payments/managers.py""" Solo Payments Manager """ import logging from datetime import datetime from django.db import models from django.http import Http404 from rest_framework.exceptions import ValidationError from plugs_payments.settings import plugs_payments_settings as settings from plugs_payments import signals LOGGER = logging.getLogger(__name__) class IfThenPaymentManager(models.Manager): """ IfThenPay Payment custom manager """ def confirmation(self, data): """ Payment confirmation callback http://www.yoursite.com/callback.php?chave=[CHAVE_ANTI_PHISHING]&entidad e=[ENTIDADE]&referencia=[REFERENCIA]&valor=[VALOR]&datahorapag=[DATA_HOR A_PAGAMENTO]&terminal=[TERMINAL] """ self.data = data self._verify_phishing_key() mbpayment = self._get_mbpayment() mbpayment.mark_as_paid(**data) return {"message": "Payment Confirmation Received"} def _verify_phishing_key(self): """ Uses key provided in the request to authenticate the payment platform """ if settings['ANTI_PHISHING_KEY'] != self.data.get('chave'): message = 'Anti Phishing Key is Missing or Incorrect.' signals.suspicious_ifthen_payment_received.send(sender=self, data=self.data) raise ValidationError(message) def _get_mbpayment(self): """ Get object or 404 """ entity = self.data.get('entidade') reference = self.data.get('referencia') value = self.data.get('valor') try: return self.model.objects.get( is_paid=False, entity=entity, reference=reference, value=value) except self.model.DoesNotExist: signals.invalid_ifthen_payment_received.send(sender=self, data=self.data) raise Http404 PKK*JL**plugs_payments/models.py""" MB Payment """ import logging from random import randint from datetime import datetime from django.utils.translation import ugettext_lazy as _ from django.db import models from plugs_core import mixins from plugs_payments.settings import plugs_payments_settings as settings from plugs_payments.managers import IfThenPaymentManager from plugs_payments.signals import valid_ifthen_payment_received from plugs_payments import fields LOGGER = logging.getLogger(__name__) class IfThenPayment(mixins.Timestampable, models.Model): """ IfThenPay Payment model """ is_paid = models.BooleanField(default=False) entity = fields.EntityField() reference = fields.ReferenceField() value = models.DecimalField(null=False, max_digits=20, decimal_places=2) payment_date = models.DateTimeField(null=True, blank=True) terminal = models.CharField(null=True, blank=True, max_length=40) objects = IfThenPaymentManager() def mark_as_paid(self, **data): """ Mark payment as paid """ self.is_paid = True # why is this a list? self.terminal = data.get('terminal')[0] self.payment_date = self._format_payment_date(data.get('datahorapag')[0]) self.save() valid_ifthen_payment_received.send(sender=self) def generate_payment_details(self): self.bookkeeper = self._generate_bookkeeper() self.entity = settings['ENTITY'] self.reference = self._generate_reference() def save(self, *args, **kwargs): """ Overrides model save method """ if not self.pk: counter = 0 while(True): # entity, reference and value must be unique together # if a record exists with the same triad, loop and # generate new payment details # incremente and log counter, in the future # can be used to set the max number of retries before # giving up try: self.generate_payment_details() data = { 'entity': self.entity, 'reference': self.reference, 'value': self.value } self.__class__.objects.get(**data) counter += 1 except self.DoesNotExist: # if the triad does not exist in the database # proceed break message = 'Retrying payments details generation {0}' LOGGER.warning(message.format(counter)) super(IfThenPayment, self).save(*args, **kwargs) def __str__(self): return str(self.reference) def _format_payment_date(self, payment_date): try: # deal with payment_date from_format = "%d-%m-%Y %H:%M:%S" to_format = "%Y-%m-%d %H:%M:%S" payment_date = datetime.strptime(payment_date, from_format).strftime(to_format) except (TypeError, ValueError): message = 'Payment date invalid. Date {0}' LOGGER.warning(message.format(payment_date)) return payment_date def _check_digits(self, integrity): """ Integrity string is used to calculate the check digits using the provided algorithm Multiply each digit of the integrity string with a corresponding value in the lookup list Sum all the multiplications and subtract the modulus of summatory with 97 to the number 98 """ multipliers = [51, 73, 17, 89, 38, 62, 45, 53, 15, 50, 5, 49, 34, 81, 76, 27, 90, 9, 30, 3] # verify input len equal to multipliers if len(integrity) != len(multipliers): raise Http404 summatory = 0 for index, digit in enumerate(integrity): summatory += int(digit) * multipliers[index] result = 98 - (summatory % 97) return '{0:02d}'.format(result) def _format_value(self): """ The value must use 8 digits, we need to multiply the decimal by 100 to shift the decimal places, convert to int and then padd the string with zeros if needed """ try: inted_value = int(self.value * 100) except TypeError: raise return '{0:08d}'.format(inted_value) def _generate_bookkeeper(self): """ Generate a random int to be used as a unique id when generating references """ return randint(0, 9999) def _generate_integrity_string(self): """ String that is gonna be used to compute check digits """ return '{0}{1}{2}{3}'.format( settings['ENTITY'], settings['SUBENTITY'], '{0:04d}'.format(self.bookkeeper), self._format_value() ) def _generate_reference(self): """ Generate a reference using the ifthenpay payment platform algorithm """ integrity = self._generate_integrity_string() check_digits = self._check_digits(integrity) subentity = str(settings['SUBENTITY']) return '{0}{1}{2}'.format(subentity, '{0:04d}'.format(self.bookkeeper), check_digits) # pylint: disable=R0903 class Meta: """ Providing verbose names is recommended if we want to use i18n in admin site """ unique_together = ('entity', 'reference', 'value') verbose_name = _('payment') verbose_name_plural = _('payments') PK-tI ^zzplugs_payments/apps.py# -*- coding: utf-8 from django.apps import AppConfig class PlugsPaymentsConfig(AppConfig): name = 'plugs_payments' PKL*J`plugs_payments/__init__.py__version__ = '0.1.1' PKJ)JCplugs_payments/settings.py""" Plugs Payments Settings """ from django.conf import settings from django.core.exceptions import ImproperlyConfigured MANDATORY_SETTINGS = ['ANTI_PHISHING_KEY', 'ENTITY', 'SUBENTITY'] PROJECT_SETTINGS = getattr(settings, 'PLUGS_PAYMENTS', {}) for setting in MANDATORY_SETTINGS: try: PROJECT_SETTINGS[setting] except KeyError: raise ImproperlyConfigured('Missing setting: PLUGS_PAYMENTS[\'{0}\']'.format(setting)) plugs_payments_settings = PROJECT_SETTINGS PK-tI,plugs_payments/static/css/plugs_payments.cssPK-tI*plugs_payments/static/js/plugs_payments.jsPKJ)J9,774plugs_payments/migrations/0002_auto_20170109_1847.py# -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2017-01-09 18:47 from __future__ import unicode_literals from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('plugs_payments', '0001_initial'), ] operations = [ migrations.RenameModel( old_name='MBPayment', new_name='IfThenPayment', ), migrations.AlterModelOptions( name='ifthenpayment', options={'verbose_name': 'payment', 'verbose_name_plural': 'payments'}, ), ] PKJ)J44plugs_payments/migrations/0004_auto_20170109_2011.py# -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2017-01-09 20:11 from __future__ import unicode_literals from django.db import migrations import plugs_payments.fields class Migration(migrations.Migration): dependencies = [ ('plugs_payments', '0003_auto_20170109_1851'), ] operations = [ migrations.AlterField( model_name='ifthenpayment', name='entity', field=plugs_payments.fields.EntityField(), ), migrations.AlterField( model_name='ifthenpayment', name='reference', field=plugs_payments.fields.ReferenceField(), ), ] PKJ)J'^j4plugs_payments/migrations/0003_auto_20170109_1851.py# -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2017-01-09 18:51 from __future__ import unicode_literals from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('plugs_payments', '0002_auto_20170109_1847'), ] operations = [ migrations.AlterUniqueTogether( name='ifthenpayment', unique_together=set([('entity', 'reference', 'value')]), ), ] PKJ)J%plugs_payments/migrations/__init__.pyPKJ)Js ll)plugs_payments/migrations/0001_initial.py# -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2017-01-09 15:03 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='MBPayment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), ('is_paid', models.BooleanField(default=False)), ('entity', models.PositiveIntegerField()), ('reference', models.PositiveIntegerField()), ('value', models.DecimalField(decimal_places=2, max_digits=5)), ('payment_date', models.DateTimeField(blank=True, null=True)), ('terminal', models.CharField(blank=True, max_length=40, null=True)), ], options={ 'abstract': False, }, ), ] PK-tI1plugs_payments/templates/plugs_payments/base.html {% comment %} As the developer of this package, don't place anything here if you can help it since this allows developers to have interoperability between your template structure and their own. Example: Developer melding the 2SoD pattern to fit inside with another pattern:: {% extends "base.html" %} {% load static %} {% block extra_js %} {% block javascript %} {% endblock javascript %} {% endblock extra_js %} {% endcomment %} PKrL*JQ$.plugs_payments-0.1.1.dist-info/DESCRIPTION.rst============================= Plugs Payments ============================= .. image:: https://badge.fury.io/py/plugs-payments.png :target: https://badge.fury.io/py/plugs-payments .. image:: https://travis-ci.org/ricardolobo/plugs-payments.png?branch=master :target: https://travis-ci.org/ricardolobo/plugs-payments Your project description goes here Documentation ------------- The full documentation is at https://plugs-payments.readthedocs.io. Quickstart ---------- Install Plugs Payments:: pip install plugs-payments Add it to your `INSTALLED_APPS`: .. code-block:: python INSTALLED_APPS = ( ... 'plugs_payments.apps.PlugsPaymentsConfig', ... ) Add Plugs Payments's URL patterns: .. code-block:: python from plugs_payments import urls as plugs_payments_urls urlpatterns = [ ... url(r'^', include(plugs_payments_urls)), ... ] Features -------- * TODO Running Tests ------------- Does the code actually work? :: source /bin/activate (myenv) $ pip install tox (myenv) $ tox Credits ------- Tools used in rendering this package: * Cookiecutter_ * `cookiecutter-djangopackage`_ .. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage History ------- 0.1.0 (2016-12-29) ++++++++++++++++++ * First release on PyPI. PKrL*JW\",plugs_payments-0.1.1.dist-info/metadata.json{"classifiers": ["Development Status :: 3 - Alpha", "Framework :: Django", "Framework :: Django :: 1.9", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "ricardolobo@soloweb.pt", "name": "Ricardo Lobo", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/ricardolobo/plugs-payments"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["plugs-payments"], "license": "MIT", "metadata_version": "2.0", "name": "plugs-payments", "run_requires": [{"requires": ["plugs-core (>=0.1.6)"]}], "summary": "Your project description goes here", "version": "0.1.1"}PKrL*JN,plugs_payments-0.1.1.dist-info/top_level.txtplugs_payments PKrL*Jndnn$plugs_payments-0.1.1.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PKrL*JO'plugs_payments-0.1.1.dist-info/METADATAMetadata-Version: 2.0 Name: plugs-payments Version: 0.1.1 Summary: Your project description goes here Home-page: https://github.com/ricardolobo/plugs-payments Author: Ricardo Lobo Author-email: ricardolobo@soloweb.pt License: MIT Keywords: plugs-payments Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Framework :: Django Classifier: Framework :: Django :: 1.9 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Requires-Dist: plugs-core (>=0.1.6) ============================= Plugs Payments ============================= .. image:: https://badge.fury.io/py/plugs-payments.png :target: https://badge.fury.io/py/plugs-payments .. image:: https://travis-ci.org/ricardolobo/plugs-payments.png?branch=master :target: https://travis-ci.org/ricardolobo/plugs-payments Your project description goes here Documentation ------------- The full documentation is at https://plugs-payments.readthedocs.io. Quickstart ---------- Install Plugs Payments:: pip install plugs-payments Add it to your `INSTALLED_APPS`: .. code-block:: python INSTALLED_APPS = ( ... 'plugs_payments.apps.PlugsPaymentsConfig', ... ) Add Plugs Payments's URL patterns: .. code-block:: python from plugs_payments import urls as plugs_payments_urls urlpatterns = [ ... url(r'^', include(plugs_payments_urls)), ... ] Features -------- * TODO Running Tests ------------- Does the code actually work? :: source /bin/activate (myenv) $ pip install tox (myenv) $ tox Credits ------- Tools used in rendering this package: * Cookiecutter_ * `cookiecutter-djangopackage`_ .. _Cookiecutter: https://github.com/audreyr/cookiecutter .. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage History ------- 0.1.0 (2016-12-29) ++++++++++++++++++ * First release on PyPI. PKrL*Jp%plugs_payments-0.1.1.dist-info/RECORDplugs_payments/__init__.py,sha256=ls1camlIoMxEZz9gSkZ1OJo-MXqHWwKPtdPbZJmwp7E,22 plugs_payments/apps.py,sha256=YZwHnXKbfKWgLMX0wQK1IMgIrguc5W6uWn4tR2-ZlOw,122 plugs_payments/fields.py,sha256=5hUu57If7N__9qqJy3bh2FmrXlH--dHUS5iJgle36PA,546 plugs_payments/managers.py,sha256=HMikD7lFOe9zkOKOWgOX1FEPVUrEJeVdp08EXYIYRIU,1900 plugs_payments/models.py,sha256=4Mvew9Co5UTrWS4HJ2uS8wfsNCYj9h1uBs7eDT4kiNQ,5674 plugs_payments/settings.py,sha256=0_P9wf6LjCnKQ7SJgXzQQFWpJ6ySR40nrGeHrbDdn2M,488 plugs_payments/signals.py,sha256=3aQ2iR8vcpmCkI7ZrNsnuX-x1wrS2XK0FS2w2OnDybo,487 plugs_payments/views.py,sha256=-oNBUzyhKCm8-cswt2PQ1p_Pp8nCojBQyfVs4r4jfY4,471 plugs_payments/migrations/0001_initial.py,sha256=j48NpMVbnmee7Piim0Is5e-8V601wFp3ecAOR4R6ICw,1132 plugs_payments/migrations/0002_auto_20170109_1847.py,sha256=SnF2ks99JQsUreGGOZk6gfQ5BSmZuGECmjJmt4LWuHQ,567 plugs_payments/migrations/0003_auto_20170109_1851.py,sha256=aoGaVg_UWlJsK3gvNpbls0S6O4XrPFX0pA0u-Tykf6Y,450 plugs_payments/migrations/0004_auto_20170109_2011.py,sha256=o5MLqmhAxP3IE05re-WVfWafvZqnQKKoa5WusO-Tihw,659 plugs_payments/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_payments/static/css/plugs_payments.css,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_payments/static/js/plugs_payments.js,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_payments/templates/plugs_payments/base.html,sha256=zzaUPjEgvwxg-Gn2rZ6gIMWphucmEGvAU_skUXJIcDA,661 plugs_payments-0.1.1.dist-info/DESCRIPTION.rst,sha256=oF5MQc53tmbllut0p0yIrZOcZc47FMeJFPADjzhnZoM,1445 plugs_payments-0.1.1.dist-info/METADATA,sha256=qlXsNF4u7EpC3rTPU5mWUlE2Z1Bc1vnZCWf4gnCRkKQ,2251 plugs_payments-0.1.1.dist-info/RECORD,, plugs_payments-0.1.1.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 plugs_payments-0.1.1.dist-info/metadata.json,sha256=MBawx2fuINqxDfd0jjo30dJYsoE6HzdF5TfwfIQreOg,953 plugs_payments-0.1.1.dist-info/top_level.txt,sha256=o2rWSDj7IMCEDZCAVcrRypZZekyeN0Q5HsFnszLJdcA,15 PKJ)J9plugs_payments/views.pyPKJ)Jɕ"" plugs_payments/fields.pyPKJ)J4dplugs_payments/signals.pyPK)J?llplugs_payments/managers.pyPKK*JL**&plugs_payments/models.pyPK-tI ^zz$plugs_payments/apps.pyPKL*J`4%plugs_payments/__init__.pyPKJ)JC%plugs_payments/settings.pyPK-tI,'plugs_payments/static/css/plugs_payments.cssPK-tI*'plugs_payments/static/js/plugs_payments.jsPKJ)J9,7744(plugs_payments/migrations/0002_auto_20170109_1847.pyPKJ)J44*plugs_payments/migrations/0004_auto_20170109_2011.pyPKJ)J'^j4-plugs_payments/migrations/0003_auto_20170109_1851.pyPKJ)J%/plugs_payments/migrations/__init__.pyPKJ)Js ll)/plugs_payments/migrations/0001_initial.pyPK-tI14plugs_payments/templates/plugs_payments/base.htmlPKrL*JQ$.7plugs_payments-0.1.1.dist-info/DESCRIPTION.rstPKrL*JW\",=plugs_payments-0.1.1.dist-info/metadata.jsonPKrL*JN,Aplugs_payments-0.1.1.dist-info/top_level.txtPKrL*Jndnn$Aplugs_payments-0.1.1.dist-info/WHEELPKrL*JO'Bplugs_payments-0.1.1.dist-info/METADATAPKrL*Jp%Kplugs_payments-0.1.1.dist-info/RECORDPKS