Beispiel #1
0
class Payment(MoneyPropped, models.Model):
    # TODO: Revise!!!
    order = models.ForeignKey("Order",
                              related_name='payments',
                              on_delete=models.PROTECT,
                              verbose_name=_('order'))
    created_on = models.DateTimeField(auto_now_add=True,
                                      verbose_name=_('created on'))
    gateway_id = models.CharField(
        max_length=32, verbose_name=_('gateway ID'))  # TODO: do we need this?
    payment_identifier = models.CharField(max_length=96,
                                          unique=True,
                                          verbose_name=_('identifier'))

    amount = MoneyProperty('amount_value', 'order.currency')
    foreign_amount = MoneyProperty('foreign_amount_value', 'foreign_currency')

    amount_value = MoneyValueField(verbose_name=_('amount'))
    foreign_amount_value = MoneyValueField(default=None,
                                           blank=True,
                                           null=True,
                                           verbose_name=_('foreign amount'))
    foreign_currency = CurrencyField(default=None,
                                     blank=True,
                                     null=True,
                                     verbose_name=_('foreign amount currency'))

    description = models.CharField(max_length=256,
                                   blank=True,
                                   verbose_name=_('description'))

    class Meta:
        verbose_name = _('payment')
        verbose_name_plural = _('payments')
Beispiel #2
0
class OrderLineTax(MoneyPropped, ShuupModel, LineTax):
    order_line = models.ForeignKey(OrderLine,
                                   related_name='taxes',
                                   on_delete=models.PROTECT,
                                   verbose_name=_('order line'))
    tax = models.ForeignKey("Tax",
                            related_name="order_line_taxes",
                            on_delete=models.PROTECT,
                            verbose_name=_('tax'))
    name = models.CharField(max_length=200, verbose_name=_('tax name'))

    amount = MoneyProperty('amount_value', 'order_line.order.currency')
    base_amount = MoneyProperty('base_amount_value',
                                'order_line.order.currency')

    amount_value = MoneyValueField(verbose_name=_('tax amount'))
    base_amount_value = MoneyValueField(
        verbose_name=_('base amount'),
        help_text=_('Amount that this tax is calculated from'))

    ordering = models.IntegerField(default=0, verbose_name=_('ordering'))

    class Meta:
        ordering = ["ordering"]

    def __str__(self):
        return "%s: %s on %s" % (self.name, self.amount, self.base_amount)

    @property
    def rounded_amount(self):
        return bankers_round(self.amount, 2)
Beispiel #3
0
class AbstractPayment(MoneyPropped, models.Model):
    created_on = models.DateTimeField(auto_now_add=True,
                                      verbose_name=_('created on'))
    gateway_id = models.CharField(
        max_length=32, verbose_name=_('gateway ID'))  # TODO: do we need this?
    payment_identifier = models.CharField(max_length=96,
                                          unique=True,
                                          verbose_name=_('identifier'))

    amount = MoneyProperty('amount_value', 'currency')
    foreign_amount = MoneyProperty('foreign_amount_value', 'foreign_currency')

    amount_value = MoneyValueField(verbose_name=_('amount'))
    foreign_amount_value = MoneyValueField(default=None,
                                           blank=True,
                                           null=True,
                                           verbose_name=_('foreign amount'))
    foreign_currency = CurrencyField(default=None,
                                     blank=True,
                                     null=True,
                                     verbose_name=_('foreign amount currency'))

    description = models.CharField(max_length=256,
                                   blank=True,
                                   verbose_name=_('description'))

    class Meta:
        abstract = True
Beispiel #4
0
class OrderLineTax(MoneyPropped, ShuupModel, LineTax):
    order_line = models.ForeignKey(OrderLine,
                                   related_name="taxes",
                                   on_delete=models.PROTECT,
                                   verbose_name=_("order line"))
    tax = models.ForeignKey("Tax",
                            related_name="order_line_taxes",
                            on_delete=models.PROTECT,
                            verbose_name=_("tax"))
    name = models.CharField(max_length=200, verbose_name=_("tax name"))

    amount = MoneyProperty("amount_value", "order_line.order.currency")
    base_amount = MoneyProperty("base_amount_value",
                                "order_line.order.currency")

    amount_value = MoneyValueField(verbose_name=_("tax amount"))
    base_amount_value = MoneyValueField(
        verbose_name=_("base amount"),
        help_text=_("Amount that this tax is calculated from."))

    ordering = models.IntegerField(default=0, verbose_name=_("ordering"))

    class Meta:
        ordering = ["ordering"]

    def __str__(self):
        return "%s: %s on %s" % (self.name, self.amount, self.base_amount)
Beispiel #5
0
class StoredBoleto(models.Model):
    order = models.OneToOneField('shuup.Order',
                                 related_name='boleto',
                                 on_delete=models.CASCADE,
                                 verbose_name=_('Pedido'))
    status = EnumIntegerField(BoletoStatus,
                              verbose_name=_('Situação'),
                              default=BoletoStatus.Created,
                              blank=True)
    bank_service = EnumField(BankService, verbose_name=_('Serviço'))
    due_date = models.DateField(verbose_name=_('Data do vencimento'))
    number_line = models.CharField(
        verbose_name=_('Linha digitável'),
        max_length=54,
        null=True,
        help_text=_("Linha digitável formatada (54 caracteres)"))
    bar_code = models.CharField(verbose_name=_('Código de barras'),
                                max_length=44,
                                null=True)
    nosso_numero = models.CharField(verbose_name=_('Nosso número'),
                                    max_length=50,
                                    null=True)
    info = JSONField(verbose_name=_('Informações do boleto'), null=True)

    total = MoneyProperty('total_value', 'order.currency')
    total_value = MoneyValueField(editable=False,
                                  verbose_name=_('Valor do Boleto'),
                                  default=0)

    payment_date = models.DateTimeField(_('Data do pagamento'),
                                        null=True,
                                        blank=True)
    payment_amount = MoneyProperty('payment_amount_value', 'order.currency')
    payment_amount_value = MoneyValueField(editable=False,
                                           verbose_name=_('Valor pago'),
                                           default=0)

    class Meta:
        verbose_name = _('Boleto bancário')
        verbose_name_plural = _('Boletos bancários')

    @property
    def html(self):
        """ Retrieves the rendered HTML for the StoredBoleto """

        html = ''

        # se for CECRED..
        if self.bank_service == BankService.CECRED:
            from python_boleto.cecred import CecredBoleto
            boleto = CecredBoleto(**self.info)
            boleto.validate()
            html = boleto.export_html()

        return html
Beispiel #6
0
class Tax(MoneyPropped, ChangeProtected, TranslatableShuupModel):
    identifier_attr = 'code'

    change_protect_message = _(
        "Cannot change business critical fields of Tax that is in use")
    unprotected_fields = ['enabled']

    code = InternalIdentifierField(
        unique=True, editable=True, verbose_name=_("code"), help_text="")

    translations = TranslatedFields(
        name=models.CharField(max_length=64, verbose_name=_("name")),
    )

    rate = models.DecimalField(
        max_digits=6, decimal_places=5, blank=True, null=True,
        verbose_name=_("tax rate"), help_text=_(
            "The percentage rate of the tax."))
    amount = MoneyProperty('amount_value', 'currency')
    amount_value = MoneyValueField(
        default=None, blank=True, null=True,
        verbose_name=_("tax amount value"), help_text=_(
            "The flat amount of the tax. "
            "Mutually exclusive with percentage rates."))
    currency = CurrencyField(
        default=None, blank=True, null=True,
        verbose_name=_("currency of tax amount"))

    enabled = models.BooleanField(default=True, verbose_name=_('enabled'))

    def clean(self):
        super(Tax, self).clean()
        if self.rate is None and self.amount is None:
            raise ValidationError(_('Either rate or amount is required'))
        if self.amount is not None and self.rate is not None:
            raise ValidationError(_('Cannot have both rate and amount'))
        if self.amount is not None and not self.currency:
            raise ValidationError(
                _("Currency is required if amount is specified"))

    def calculate_amount(self, base_amount):
        """
        Calculate tax amount with this tax for given base amount.

        :type base_amount: shuup.utils.money.Money
        :rtype: shuup.utils.money.Money
        """
        if self.amount is not None:
            return self.amount
        if self.rate is not None:
            return self.rate * base_amount
        raise ValueError("Improperly configured tax: %s" % self)

    def __str__(self):
        text = super(Tax, self).__str__()
        if self.rate is not None:
            text += " ({})".format(format_percent(self.rate, digits=3))
        if self.amount is not None:
            text += " ({})".format(format_money(self.amount))
        return text

    def _are_changes_protected(self):
        return self.order_line_taxes.exists()

    class Meta:
        verbose_name = _('tax')
        verbose_name_plural = _('taxes')
Beispiel #7
0
    class Wallet(object):
        amount = MoneyProperty('value', 'currency')

        def __init__(self):
            self.value = 42
            self.currency = 'EUR'
Beispiel #8
0
class Tax(MoneyPropped, ChangeProtected, TranslatableShuupModel):
    identifier_attr = 'code'

    change_protect_message = _(
        "Can't change the business critical fields of the Tax that is in use.")
    unprotected_fields = ['enabled']

    code = InternalIdentifierField(
        unique=True,
        editable=True,
        verbose_name=_("code"),
        help_text=_("The abbreviated tax code name."))

    translations = TranslatedFields(name=models.CharField(
        max_length=124,
        verbose_name=_("name"),
        help_text=
        _("The name of the tax. It is shown in order lines, in order invoices and confirmations."
          )), )

    rate = models.DecimalField(
        max_digits=6,
        decimal_places=5,
        blank=True,
        null=True,
        verbose_name=_("tax rate"),
        help_text=
        _("The percentage rate of the tax. "
          "Mutually exclusive with the flat amount tax (flat tax is rarely used "
          "and the option is therefore hidden by default; contact Shuup to enable)."
          ))
    amount = MoneyProperty('amount_value', 'currency')
    amount_value = MoneyValueField(
        default=None,
        blank=True,
        null=True,
        verbose_name=_("tax amount value"),
        help_text=_("The flat amount of the tax. "
                    "Mutually exclusive with percentage rates tax."))
    currency = CurrencyField(default=None,
                             blank=True,
                             null=True,
                             verbose_name=_("currency of the amount tax"))

    enabled = models.BooleanField(
        default=True,
        verbose_name=_('enabled'),
        help_text=_("Enable if this tax is valid and should be active."))

    def clean(self):
        super(Tax, self).clean()
        if self.rate is None and self.amount is None:
            raise ValidationError(_("Either rate or amount tax is required."))
        if self.amount is not None and self.rate is not None:
            raise ValidationError(
                _("Can't have both rate and amount taxes. They are mutually exclusive."
                  ))
        if self.amount is not None and not self.currency:
            raise ValidationError(
                _("Currency is required if the amount tax value is specified.")
            )

    def calculate_amount(self, base_amount):
        """
        Calculate tax amount with this tax for a given base amount.

        :type base_amount: shuup.utils.money.Money
        :rtype: shuup.utils.money.Money
        """
        if self.amount is not None:
            return self.amount
        if self.rate is not None:
            return self.rate * base_amount
        raise ValueError(
            "Error! Calculations of the tax amount failed. Improperly configured tax: %s."
            % self)

    def __str__(self):
        text = super(Tax, self).__str__()
        if self.rate is not None:
            text += " ({})".format(format_percent(self.rate, digits=3))
        if self.amount is not None:
            text += " ({})".format(format_money(self.amount))
        return text

    def _are_changes_protected(self):
        return self.order_line_taxes.exists()

    class Meta:
        verbose_name = _('tax')
        verbose_name_plural = _('taxes')
Beispiel #9
0
class CieloTransaction(models.Model):
    TIMEOUT_SECONDS = 5

    shop = models.ForeignKey(Shop, verbose_name=_("shop"))
    order_transaction = models.OneToOneField(CieloOrderTransaction,
                                             related_name="transaction",
                                             verbose_name=_("Cielo Order"))
    tid = models.CharField(_('Transaction ID'), max_length=50)
    status = EnumIntegerField(CieloTransactionStatus,
                              verbose_name=_('Transaction status'),
                              default=CieloTransactionStatus.NotCreated,
                              blank=True)
    creation_date = models.DateTimeField(_('Creation date'), auto_now_add=True)
    last_update = models.DateTimeField(_('Last update'), auto_now=True)

    cc_holder = models.CharField(_('Card holder'), max_length=50)
    cc_brand = models.CharField(_('Card brand'), max_length=30)
    installments = models.PositiveSmallIntegerField(_('Installments'),
                                                    default=1)
    cc_product = models.CharField(_('Product'),
                                  max_length=30,
                                  choices=CIELO_PRODUCT_CHOICES)

    total = MoneyProperty('total_value', 'order_transaction.order.currency')
    total_captured = MoneyProperty('total_captured_value',
                                   'order_transaction.order.currency')
    total_reversed = MoneyProperty('total_reversed_value',
                                   'order_transaction.order.currency')
    intereset = MoneyProperty('interest_value',
                              'order_transaction.order.currency')

    total_value = MoneyValueField(editable=False,
                                  verbose_name=_('transaction total'),
                                  default=0)
    total_captured_value = MoneyValueField(editable=True,
                                           verbose_name=_('total captured'),
                                           default=0)
    total_reversed_value = MoneyValueField(editable=True,
                                           verbose_name=_('total reversed'),
                                           default=0)
    interest_value = MoneyValueField(editable=False,
                                     verbose_name=_('interest amount'),
                                     default=0)

    authorization_lr = models.CharField(_('Authorization LR code'),
                                        max_length=2,
                                        blank=True)
    authorization_nsu = models.CharField(_('Authorization NSU'),
                                         max_length=50,
                                         blank=True,
                                         null=True)
    authorization_date = models.DateTimeField(_('Authorization date'),
                                              null=True,
                                              blank=True)

    authentication_eci = models.SmallIntegerField(_('ECI security level'),
                                                  null=True,
                                                  default=0)
    authentication_date = models.DateTimeField(_('Authentication date'),
                                               null=True,
                                               blank=True)

    international = models.BooleanField(_('International transaction'),
                                        default=False)

    class Meta:
        verbose_name = _('Cielo 1.5 transaction')
        verbose_name_plural = _('Cielo 1.5 transactions')

    def __str__(self):
        return "CieloTransaction TID={0}".format(self.tid)

    def _get_comercial(self):
        cielo_config = self.shop.cielo_config
        return Comercial(numero=safe_int(cielo_config.ec_num),
                         chave=cielo_config.ec_key)

    def _get_cielo_request(self):
        return CieloRequest(sandbox=self.shop.cielo_config.sandbox)

    def refresh(self):
        '''
        Updates this transaction info with Cielo server

        :return: wheter the synchronization was successful
        '''

        # consuta a transação
        cielo_request = self._get_cielo_request()

        try:
            response_transaction = cielo_request.consultar(
                tid=self.tid, comercial=self._get_comercial())
            self._update_from_transaction(response_transaction)
            return True

        except:
            logger.exception(_('Fail to update Cielo transation info'))

        return False

    def _update_from_transaction(self, response_transaction):
        if response_transaction.status:
            self.status = response_transaction.status

        if response_transaction.autorizacao:
            self.authorization_lr = response_transaction.autorizacao.lr
            self.authorization_nsu = response_transaction.autorizacao.nsu
            self.authorization_date = iso8601.parse_date(
                response_transaction.autorizacao.data_hora)
            self.international = (response_transaction.autorizacao.lr == "11")

        if response_transaction.autenticacao:
            self.authentication_eci = response_transaction.autenticacao.eci
            self.authentication_date = iso8601.parse_date(
                response_transaction.autenticacao.data_hora)

        if response_transaction.captura:
            self.total_captured_value = Decimal(
                response_transaction.captura.valor / 100.0)

        if response_transaction.cancelamento:
            self.total_reversed_value = Decimal(
                response_transaction.cancelamento.valor / 100.0)

        self.save()

    def capture(self, amount):
        '''
        Captures a total or partial amout of this transaction
        :type: amount: decimal.Decimal
        '''

        cielo_request = self._get_cielo_request()
        response_transaction = cielo_request.capturar(
            tid=self.tid,
            comercial=self._get_comercial(),
            valor=decimal_to_int_cents(amount))
        self._update_from_transaction(response_transaction)

    def safe_cancel(self, amount):
        """
        Safe transaction cancel (try, catch enclosed)
        :return: success or not
        :rtype: bool
        """
        try:
            self.cancel(amount)
            return True
        except:
            logger.exception(
                _("Failed to cancel transaction {0}").format(self.tid))
            return False

    def cancel(self, amount):
        '''
        Cancel a total or partial amout of this transaction
        :type: amount: decimal.Decimal
        '''
        cielo_request = self._get_cielo_request()
        response_transaction = cielo_request.cancelar(
            tid=self.tid,
            comercial=self._get_comercial(),
            valor=decimal_to_int_cents(amount))
        self._update_from_transaction(response_transaction)