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')
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)
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
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)
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
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')
class Wallet(object): amount = MoneyProperty('value', 'currency') def __init__(self): self.value = 42 self.currency = 'EUR'
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')
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)