Fixes on DD payment method
This commit is contained in:
parent
7a392df70d
commit
6902f9e559
|
@ -1,5 +1,6 @@
|
|||
from django.conf import settings as django_settings
|
||||
from django.conf import settings as djsettings
|
||||
from django.db import models
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.core import services
|
||||
|
@ -8,9 +9,10 @@ from . import settings
|
|||
|
||||
|
||||
class Account(models.Model):
|
||||
user = models.OneToOneField(django_settings.AUTH_USER_MODEL, related_name='accounts')
|
||||
type = models.CharField(_("type"), max_length=32, choices=settings.ACCOUNTS_TYPES,
|
||||
default=settings.ACCOUNTS_DEFAULT_TYPE)
|
||||
user = models.OneToOneField(djsettings.AUTH_USER_MODEL,
|
||||
verbose_name=_("user"), related_name='accounts')
|
||||
type = models.CharField(_("type"), choices=settings.ACCOUNTS_TYPES,
|
||||
max_length=32, default=settings.ACCOUNTS_DEFAULT_TYPE)
|
||||
language = models.CharField(_("language"), max_length=2,
|
||||
choices=settings.ACCOUNTS_LANGUAGES,
|
||||
default=settings.ACCOUNTS_DEFAULT_LANGUAGE)
|
||||
|
@ -21,10 +23,9 @@ class Account(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def name(self):
|
||||
self._cached_name = getattr(self, '_cached_name', self.user.username)
|
||||
return self._cached_name
|
||||
return self.user.username
|
||||
|
||||
|
||||
services.register(Account, menu=False)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf import settings as django_settings
|
||||
from django.conf import settings as djsettings
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -56,10 +56,10 @@ class Ticket(models.Model):
|
|||
(CLOSED, 'Closed'),
|
||||
)
|
||||
|
||||
creator = models.ForeignKey(django_settings.AUTH_USER_MODEL, verbose_name=_("created by"),
|
||||
creator = models.ForeignKey(djsettings.AUTH_USER_MODEL, verbose_name=_("created by"),
|
||||
related_name='tickets_created', null=True)
|
||||
creator_name = models.CharField(_("creator name"), max_length=256, blank=True)
|
||||
owner = models.ForeignKey(django_settings.AUTH_USER_MODEL, null=True, blank=True,
|
||||
owner = models.ForeignKey(djsettings.AUTH_USER_MODEL, null=True, blank=True,
|
||||
related_name='tickets_owned', verbose_name=_("assigned to"))
|
||||
queue = models.ForeignKey(Queue, related_name='tickets', null=True, blank=True)
|
||||
subject = models.CharField(_("subject"), max_length=256)
|
||||
|
@ -153,7 +153,7 @@ class Ticket(models.Model):
|
|||
class Message(models.Model):
|
||||
ticket = models.ForeignKey('issues.Ticket', verbose_name=_("ticket"),
|
||||
related_name='messages')
|
||||
author = models.ForeignKey(django_settings.AUTH_USER_MODEL, verbose_name=_("author"),
|
||||
author = models.ForeignKey(djsettings.AUTH_USER_MODEL, verbose_name=_("author"),
|
||||
related_name='ticket_messages')
|
||||
author_name = models.CharField(_("author name"), max_length=256, blank=True)
|
||||
content = models.TextField(_("content"))
|
||||
|
@ -183,7 +183,7 @@ class TicketTracker(models.Model):
|
|||
""" Keeps track of user read tickets """
|
||||
ticket = models.ForeignKey(Ticket, verbose_name=_("ticket"),
|
||||
related_name='trackers')
|
||||
user = models.ForeignKey(django_settings.AUTH_USER_MODEL, verbose_name=_("user"),
|
||||
user = models.ForeignKey(djsettings.AUTH_USER_MODEL, verbose_name=_("user"),
|
||||
related_name='ticket_trackers')
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
from django.contrib import admin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.admin.utils import admin_colored, admin_link
|
||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||
|
||||
from .actions import process_transactions
|
||||
from .methods import BankTransfer
|
||||
from .models import PaymentSource, Transaction
|
||||
from .models import PaymentSource, Transaction, PaymentProcess
|
||||
|
||||
|
||||
STATE_COLORS = {
|
||||
|
@ -22,6 +25,7 @@ class TransactionAdmin(admin.ModelAdmin):
|
|||
'id', 'bill_link', 'account_link', 'source', 'display_state', 'amount'
|
||||
)
|
||||
list_filter = ('source__method', 'state')
|
||||
actions = (process_transactions,)
|
||||
|
||||
bill_link = admin_link('bill')
|
||||
account_link = admin_link('bill__account')
|
||||
|
@ -35,5 +39,29 @@ class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin):
|
|||
# TODO select payment source method
|
||||
|
||||
|
||||
class PaymentProcessAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'file_url', 'display_transactions', 'created_at')
|
||||
fields = ('data', 'file_url', 'display_transactions', 'created_at')
|
||||
readonly_fields = ('file_url', 'display_transactions', 'created_at')
|
||||
|
||||
def file_url(self, process):
|
||||
if process.file:
|
||||
return '<a href="%s">%s</a>' % (process.file.url, process.file.name)
|
||||
file_url.allow_tags = True
|
||||
file_url.admin_order_field = 'file'
|
||||
|
||||
def display_transactions(self, process):
|
||||
links = []
|
||||
for transaction in process.transactions.all():
|
||||
url = reverse('admin:payments_transaction_change', args=(transaction.pk,))
|
||||
links.append(
|
||||
'<a href="%s">%s</a>' % (url, str(transaction))
|
||||
)
|
||||
return '<br>'.join(links)
|
||||
display_transactions.short_description = _("Transactions")
|
||||
display_transactions.allow_tags = True
|
||||
|
||||
|
||||
admin.site.register(PaymentSource, PaymentSourceAdmin)
|
||||
admin.site.register(Transaction, TransactionAdmin)
|
||||
admin.site.register(PaymentProcess, PaymentProcessAdmin)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import random
|
||||
import string
|
||||
import os
|
||||
import lxml.builder
|
||||
from lxml import etree
|
||||
from lxml.builder import E
|
||||
from StringIO import StringIO
|
||||
|
||||
from django.conf import settings as djsettings
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_iban.validators import IBANValidator, IBAN_COUNTRY_CODE_LENGTH
|
||||
|
@ -52,28 +54,28 @@ class BankTransfer(PaymentMethod):
|
|||
form = BankTransferForm
|
||||
serializer = BankTransferSerializer
|
||||
|
||||
def set_id(self):
|
||||
size=6
|
||||
chars=string.ascii_uppercase + string.digits
|
||||
self.payment_id = ''.join(random.choice(chars) for _ in range(size))
|
||||
|
||||
def _process_transactions(self, transactions):
|
||||
for transaction in transactions:
|
||||
account = transaction.account
|
||||
data = transaction.data
|
||||
transaction.info = self.payment_id
|
||||
self.object.transactions.add(transaction)
|
||||
# TODO transaction.account
|
||||
account = transaction.bill.account
|
||||
# FIXME
|
||||
data = account.payment_sources.first().data
|
||||
transaction.state = transaction.WAITTING_CONFIRMATION
|
||||
transaction.save()
|
||||
yield E.DrctDbtTxInf( # Direct Debit Transaction Info
|
||||
E.PmtId( # Payment Id
|
||||
E.EndToEndId(str(transaction.id)) # Payment Id/End to End
|
||||
),
|
||||
E.InstdAmt(transaction.amount, Ccy="EUR"), # Instructed Amount
|
||||
E.InstdAmt( # Instructed Amount
|
||||
str(transaction.amount),
|
||||
Ccy=transaction.currency.upper()
|
||||
),
|
||||
E.DrctDbtTx( # Direct Debit Transaction
|
||||
E.MndtRltdInf( # Mandate Related Info
|
||||
E.MndtId(str(account.id)), # Mandate Id
|
||||
E.DtOfSgntr( # Date of Signature
|
||||
account.registered_on.strfrm("%Y-%m-%d")
|
||||
account.register_date.strftime("%Y-%m-%d")
|
||||
)
|
||||
)
|
||||
),
|
||||
|
@ -95,17 +97,24 @@ class BankTransfer(PaymentMethod):
|
|||
)
|
||||
|
||||
def process(self, transactions):
|
||||
self.set_id()
|
||||
from .models import PaymentProcess
|
||||
self.object = PaymentProcess.objects.create()
|
||||
creditor_name = settings.PAYMENTS_DD_CREDITOR_NAME
|
||||
creditor_iban = settings.PAYMENTS_DD_CREDITOR_IBAN
|
||||
creditor_bic = settings.PAYMENTS_DD_CREDITOR_BIC
|
||||
creditor_at02_id = settings.PAYMENTS_DD_CREDITOR_AT02_ID
|
||||
now = timezone.now()
|
||||
total = str(sum([transaction.amount for transaction in transactions]))
|
||||
sepa = E.Document(
|
||||
sepa = lxml.builder.ElementMaker(
|
||||
nsmap = {
|
||||
'xsi': "http://www.w3.org/2001/XMLSchema-instance",
|
||||
None: "urn:iso:std:iso:20022:tech:xsd:pain.008.001.02",
|
||||
}
|
||||
)
|
||||
sepa = sepa.Document(
|
||||
E.CstmrDrctDbtInitn(
|
||||
E.GrpHdr( # Group Header
|
||||
E.MsgId(self.payment_id), # Message Id
|
||||
E.MsgId(str(self.object.id)), # Message Id
|
||||
E.CreDtTm(now.strftime("%Y-%m-%dT%H:%M:%S")), # Creation Date Time
|
||||
E.NbOfTxs(str(len(transactions))), # Number of Transactions
|
||||
E.CtrlSum(total), # Control Sum
|
||||
|
@ -114,14 +123,14 @@ class BankTransfer(PaymentMethod):
|
|||
E.Id( # Identification
|
||||
E.OrgId( # Organisation Id
|
||||
E.Othr(
|
||||
E.Id(creditor_at_02)
|
||||
E.Id(creditor_at02_id)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
E.PmtInf( # Payment Info
|
||||
E.PmtInfId(self.payment_id), # Payment Id
|
||||
E.PmtInfId(str(self.object.id)), # Payment Id
|
||||
E.PmtMtd("DD"), # Payment Method
|
||||
E.NbOfTxs(str(len(transactions))), # Number of Transactions
|
||||
E.CtrlSum(total), # Control Sum
|
||||
|
@ -134,7 +143,7 @@ class BankTransfer(PaymentMethod):
|
|||
),
|
||||
E.SeqTp("RCUR") # Sequence Type
|
||||
),
|
||||
E.ReqdColltnDt(now.strfrm("%Y-%m-%d")), # Requested Collection Date
|
||||
E.ReqdColltnDt(now.strftime("%Y-%m-%d")), # Requested Collection Date
|
||||
E.Cdtr( # Creditor
|
||||
E.Nm(creditor_name)
|
||||
),
|
||||
|
@ -150,19 +159,24 @@ class BankTransfer(PaymentMethod):
|
|||
),
|
||||
*list(self._process_transactions(transactions)) # Transactions
|
||||
)
|
||||
), {
|
||||
'xmlns': "urn:iso:std:iso:20022:tech:xsd:pain.008.001.02",
|
||||
'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance"
|
||||
}
|
||||
)
|
||||
)
|
||||
# http://www.iso20022.org/documents/messages/1_0_version/pain/schemas/pain.008.001.02.zip
|
||||
schema = etree.parse('pain.008.001.02.xsd')
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
xsd_path = os.path.join(path, 'pain.008.001.02.xsd')
|
||||
schema_doc = etree.parse(xsd_path)
|
||||
schema = etree.XMLSchema(schema_doc)
|
||||
sepa = etree.parse(StringIO(etree.tostring(sepa)))
|
||||
schema.assertValid(sepa)
|
||||
# TODO where to save this shit?
|
||||
# TODO new model? Payment with batch support, How this relates to transaction?
|
||||
# TODO positive only amount ?
|
||||
# TODO what with negative amounts? what are amendments?
|
||||
return etree.tostring(page, pretty_print=True, xml_declaration=True)
|
||||
base_path = self.object.file.field.upload_to or djsettings.MEDIA_ROOT
|
||||
file_name = 'payment-process-%i.xml' % self.object.id
|
||||
file_path = os.path.join(base_path, file_name)
|
||||
sepa.write(file_path,
|
||||
pretty_print=True,
|
||||
xml_declaration=True,
|
||||
encoding='UTF-8')
|
||||
self.object.file = file_name
|
||||
self.object.save()
|
||||
|
||||
|
||||
class CreditCard(PaymentMethod):
|
||||
|
|
|
@ -61,7 +61,6 @@ class Transaction(models.Model):
|
|||
related_name='transactions')
|
||||
state = models.CharField(_("state"), max_length=32, choices=STATES,
|
||||
default=WAITTING_PROCESSING)
|
||||
data = JSONField(_("data"))
|
||||
amount = models.DecimalField(_("amount"), max_digits=12, decimal_places=2)
|
||||
currency = models.CharField(max_length=10, default=settings.PAYMENT_CURRENCY)
|
||||
created_on = models.DateTimeField(auto_now_add=True)
|
||||
|
@ -72,5 +71,19 @@ class Transaction(models.Model):
|
|||
return "Transaction {}".format(self.id)
|
||||
|
||||
|
||||
class PaymentProcess(models.Model):
|
||||
"""
|
||||
Stores arbitrary data generated by payment methods while processing transactions
|
||||
"""
|
||||
transactions = models.ManyToManyField(Transaction, related_name='processes',
|
||||
verbose_name=_("transactions"))
|
||||
data = JSONField(_("data"), blank=True)
|
||||
file = models.FileField(_("file"), blank=True)
|
||||
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return str(self.id)
|
||||
|
||||
|
||||
accounts.register(PaymentSource)
|
||||
accounts.register(Transaction)
|
||||
|
|
|
@ -7,8 +7,8 @@ PAYMENT_CURRENCY = getattr(settings, 'PAYMENT_CURRENCY', 'Eur')
|
|||
PAYMENTS_DD_CREDITOR_NAME = getattr(settings, 'PAYMENTS_DD_CREDITOR_NAME',
|
||||
'Orchestra')
|
||||
PAYMENTS_DD_CREDITOR_IBAN = getattr(settings, 'PAYMENTS_DD_CREDITOR_IBAN',
|
||||
'InvalidIBAN')
|
||||
'IE98BOFI90393912121212')
|
||||
PAYMENTS_DD_CREDITOR_BIC = getattr(settings, 'PAYMENTS_DD_CREDITOR_BIC',
|
||||
'InvalidBIC')
|
||||
'BOFIIE2D')
|
||||
PAYMENTS_DD_CREDITOR_AT02_ID = getattr(settings, 'PAYMENTS_DD_CREDITOR_AT02_ID',
|
||||
'InvalidAT02ID')
|
||||
|
|
|
@ -31,6 +31,7 @@ USE_TZ = True
|
|||
# Example: "http://media.lawrence.com/static/"
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
ALLOWED_HOSTS = '*'
|
||||
|
||||
|
|
|
@ -51,6 +51,10 @@ DATABASES = {
|
|||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
|
||||
|
||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
|
||||
|
||||
# EMAIL_HOST = 'smtp.yourhost.eu'
|
||||
# EMAIL_PORT = ''
|
||||
# EMAIL_HOST_USER = ''
|
||||
|
|
|
@ -21,6 +21,10 @@ urlpatterns = patterns('',
|
|||
'rest_framework.authtoken.views.obtain_auth_token',
|
||||
name='api-token-auth'
|
||||
),
|
||||
# TODO make this private
|
||||
url(r'^media/(?P<path>.*)$', 'django.views.static.serve',
|
||||
{'document_root': settings.MEDIA_ROOT, 'show_indexes': True}
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue