Source code for polaris.integrations.forms
from django import forms
from django.contrib.humanize.templatetags.humanize import intcomma
from django.utils.translation import gettext_lazy as _
from django.forms.widgets import TextInput
from polaris.models import Transaction, Asset
class CardNumberInput(TextInput):
template_name = "widgets/card_number.html"
class CardExpirationInput(TextInput):
template_name = "widgets/card_expiration.html"
class CardCvvInput(TextInput):
template_name = "widgets/card_cvv.html"
class CreditCardField(forms.CharField):
def __init__(self, placeholder=None, *args, **kwargs):
super().__init__(
# override default widget
widget=CardNumberInput(attrs={"placeholder": placeholder}),
*args,
**kwargs,
)
default_error_messages = {
"invalid": _("The credit card number is invalid"),
}
@staticmethod
def luhn_checksum(card_number):
def digits_of(n):
return [int(d) for d in str(n)]
digits = digits_of(card_number)
odd_digits = digits[-1::-2]
even_digits = digits[-2::-2]
checksum = 0
checksum += sum(odd_digits)
for ed in even_digits:
checksum += sum(digits_of(ed * 2))
return checksum % 10
def is_luhn_valid(self, card_number):
return self.luhn_checksum(card_number) == 0
def clean(self, value):
# ensure no spaces or dashes
value = value.replace(" ", "").replace("-", "")
if not (value.isdigit() and self.is_luhn_valid(value)):
raise forms.ValidationError(self.error_messages["invalid"])
return value
[docs]class CreditCardForm(forms.Form):
"""
A generic form for collecting credit or debit card information.
Ensures `card_number` is valid, but does not validate the `expiration` or
`cvv`. Subclass this form for additional validation.
"""
name = forms.CharField(label=_("Name"))
card_number = CreditCardField(label=_("Card Number"))
expiration = forms.Field(widget=CardExpirationInput, label=_("Expiration"))
cvv = forms.Field(widget=CardCvvInput, label=_("CVV"))
[docs]class TransactionForm(forms.Form):
"""
.. _`HiddenInput`: https://docs.djangoproject.com/en/3.0/ref/forms/widgets/#hiddeninput
Base class for collecting transaction information.
Developers must define subclasses to collect additional information and
apply additional validation.
A subclass of this form should be returned by
``form_for_transaction()`` once for each interactive flow.
After form validation, the key-value pairs in `self.cleaned_data` will be
passed to the registered fee function to calculate `amount_fee` for the
associated ``Transaction``. If you want a key-value pair passed to
the registered fee function but don't want to display an additional field
to the user, add a field with a `HiddenInput`_ widget.
The `amount` field is validated with the :meth:`clean_amount` function,
which ensures the amount is within the bounds for the asset type.
"""
def __init__(self, transaction: Transaction, *args, **kwargs):
# For testing via anchor-validator.herokuapp.com
test_value = kwargs.pop("test_value", None)
if not test_value:
test_value = (
"103" if transaction.kind == Transaction.KIND.deposit else "100"
)
super().__init__(*args, **kwargs)
self.transaction = transaction
self.asset = transaction.asset
self.decimal_places = self.asset.significant_decimals
if transaction.kind == Transaction.KIND.deposit:
self.min_amount = round(self.asset.deposit_min_amount, self.decimal_places)
self.max_amount = round(self.asset.deposit_max_amount, self.decimal_places)
self.min_default = Asset._meta.get_field("deposit_min_amount").default
self.max_default = Asset._meta.get_field("deposit_max_amount").default
else:
self.min_amount = round(
self.asset.withdrawal_min_amount, self.decimal_places
)
self.max_amount = round(
self.asset.withdrawal_max_amount, self.decimal_places
)
self.min_default = Asset._meta.get_field("withdrawal_min_amount").default
self.max_default = Asset._meta.get_field("withdrawal_max_amount").default
# Re-initialize the 'amount' field now that we have all the parameters necessary
self.fields["amount"].__init__(
widget=forms.NumberInput(
attrs={
"class": "input",
"inputmode": "decimal",
"symbol": self.asset.symbol,
"test-value": test_value,
}
),
min_value=self.min_amount,
max_value=self.max_amount,
decimal_places=self.decimal_places,
label=_("Amount"),
localize=True,
)
limit_str = ""
if self.min_amount > self.min_default and self.max_amount < self.max_default:
limit_str = f"({intcomma(self.min_amount)} - {intcomma(self.max_amount)})"
elif self.min_amount > self.min_default:
limit_str = _("(minimum: %s)") % intcomma(self.min_amount)
elif self.max_amount < self.max_default:
limit_str = _("(maximum: %s)") % intcomma(self.max_amount)
if limit_str:
self.fields["amount"].label += " " + limit_str
amount = forms.DecimalField()
def clean_amount(self):
"""Validate the provided amount of an asset."""
amount = round(self.cleaned_data["amount"], self.decimal_places)
if amount < self.min_amount:
raise forms.ValidationError(
_("The minimum amount is: %s")
% intcomma(round(self.min_amount, self.decimal_places))
)
elif amount > self.max_amount:
raise forms.ValidationError(
_("The maximum amount is: %s")
% intcomma(round(self.max_amount, self.decimal_places))
)
return amount