Beispiel #1
0
    def generate_batch(self):
        sepa = SepaDD(settings.SEPA_CONFIG,schema="pain.008.001.02", clean=True)
        payments = []

        for batch_result in SepaBatchResult.objects.filter(batch=self):
            payment = batch_result.payment

            if payment.amount <= 0:
                batch_result.success = False
                batch_result.fail_reason = ZERO_AMOUNT

            if not payment.account or not payment.account.iban_code:
                batch_result.success = False
                batch_result.fail_reason = IBAN_MISSING

            else:
                batch_result.iban_code = payment.account.iban_code[4:8]
                bank = BankBICCode.objects.filter(bank_code=batch_result.iban_code).first()
                if not bank:
                    batch_result.success = False
                    batch_result.fail_reason = BIC_MISSING

            if batch_result.success:
                batch_result.bic_code = bank.bic_code
                batch_result.bank_name = bank.bank_name
                pay = {
                    "name": payment.account.display_name,
                    "IBAN": payment.account.iban_code,
                    "amount": int(payment.amount * 100),
                    "BIC":  batch_result.bic_code,
                    "type": "RCUR",
                    "collection_date": datetime.date.today(),
                    "mandate_id": payment.account.cif,
                    "execution_date": datetime.date.today(),
                    "mandate_date": datetime.date.today(),
                    "description": payment.concept,
                    "endtoend_id": str(payment.reference).replace('-',''),
                }
                sepa.add_payment(pay)
                payments.append(batch_result.payment)

            batch_result.save()

        if (len(payments) > 0):
            sepa_xml = sepa.export(validate=True)
            xml_temp = NamedTemporaryFile()
            xml_temp.write(sepa_xml)
            xml_temp.flush()

            self.sepa_file.save(f"sepa_batch_{self.pk}.xml", File(xml_temp))
            self.save()

            # We check the included payments as paid
            for payment in payments:
                payment.completed = True
                payment.timestamp = datetime.datetime.now()
                payment.type = DEBIT
                payment.save()
Beispiel #2
0
 def __init__(self):
     self.invoices = []
     self.config = {
         "name": app.config.get('SEPADD_CREDITOR_NAME'),
         "IBAN": app.config.get('SEPADD_CREDITOR_IBAN'),
         "BIC": app.config.get('SEPADD_CREDITOR_BIC'),
         "batch": app.config.get('SEPADD_BATCH'),
         "creditor_id": app.config.get('SEPADD_CREDITOR_ID'),
         "currency": app.config.get('SEPADD_CURRENCY'),
         "instrument": app.config.get('SEPADD_INSTRUMENT')
     }
     self.sepa = SepaDD(self.config, schema=app.config.get('SEPADD_SCHEMA'))
Beispiel #3
0
def test_name_too_long():
    sdd = SepaDD({
        "name": "TestCreditor",
        "BIC": "BANKNL2A",
        "IBAN": "NL50BANK1234567890",
        "batch": True,
        "creditor_id": "000000",
        "currency": "EUR"
    })
    payment1 = {
        "name":
        "Test von Testenstein Test von Testenstein Test von Testenstein",
        "IBAN": "NL50BANK1234567890",
        "BIC": "BANKNL2A",
        "amount": 1012,
        "type": "FRST",
        "collection_date": datetime.date.today(),
        "mandate_id": "1234",
        "mandate_date": datetime.date.today(),
        "description": "Test transaction1"
    }
    sdd.add_payment(payment1)
    with pytest.raises(ValidationError):
        sdd.export()
    sdd.export(validate=False)
Beispiel #4
0
    def generate_batch(self):
        sepa = SepaDD(settings.SEPA_CONFIG,
                      schema="pain.008.001.02",
                      clean=True)
        payments = []

        for batch_result in SepaBatchResult.objects.filter(batch=self):
            payment = batch_result.payment

            if batch_result.success:
                pay = {
                    "name": payment.account.display_name,
                    "IBAN": payment.account.iban_code,
                    "amount": int(payment.amount * 100),
                    "BIC": batch_result.bic_code,
                    "type": "RCUR",
                    "collection_date": datetime.date.today(),
                    "mandate_id": payment.account.cif,
                    "execution_date": datetime.date.today(),
                    "mandate_date": datetime.date.today(),
                    "description": payment.concept,
                    "endtoend_id": str(payment.reference).replace('-', ''),
                }
                sepa.add_payment(pay)
                payments.append(batch_result.payment)

                payment.invoice_prefix = self.invoice_prefix
                payment.invoice_number = batch_result.invoice_number
                payment.invoice_date = self.attempt
                payment.save()

        if (len(payments) > 0):
            sepa_xml = sepa.export(validate=True)
            xml_temp = NamedTemporaryFile()
            xml_temp.write(sepa_xml)
            xml_temp.flush()

            self.sepa_file.save(f"sepa_batch_{self.pk}.xml", File(xml_temp))
            self.save()

            # We update the payments date
            for payment in payments:
                #payment.added = datetime.datetime.now()
                payment.type = DEBIT
                payment.save()
Beispiel #5
0
def test_valid_config():
    return SepaDD({
        "name": "TestCreditor",
        "IBAN": "NL50BANK1234567890",
        "BIC": "BANKNL2A",
        "batch": True,
        "creditor_id": "000000",
        "currency": "EUR"
    })
Beispiel #6
0
def test_invalid_config():
    with pytest.raises(Exception):
        return SepaDD({
            "name": "TestCreditor",
            "BIC": "BANKNL2A",
            "batch": True,
            "creditor_id": "000000",
            "currency": "EUR"
        })
def sdd():
    return SepaDD(
        {
            "name": "TestCreditor",
            "IBAN": "NL50BANK1234567890",
            "BIC": "BANKNL2A",
            "batch": True,
            "creditor_id": "DE26ZZZ00000000000",
            "currency": "EUR"
        },
        schema="pain.008.003.02")
def create_sepa_xml(input_file: IO[str], output_path: str,
                    config: Dict[str, Any]) -> None:
    """Creates a XML file from CSV data.

    Parameters
    ----------
    input_file: IO[str]
        Path to the CSV data.
    output_path: str
        Path to the output data.
        Default is in the project-root/output.
    config: Dict[str, Any]
        Config.json for SEPA XML generation.
    """
    today_s = str(date.today())
    hit_credit = False
    hit_debit = False

    sepa_credit = SepaTransfer(config, clean=True)
    sepa_debit = SepaDD(config, schema='pain.008.002.02', clean=True)

    echo('Getting Data...')
    for i, payment in enumerate(single_csv_row(input_file)):
        p, p_type = _pack_data(payment)

        if p_type == 'credit':
            hit_credit = True
            sepa_credit.add_payment(p)
        if p_type == 'debit':
            hit_debit = True
            sepa_debit.add_payment(p)

    if hit_credit:
        _generate_output(today_s + '_ueberweisung', sepa_credit.export(),
                         output_path)
    if hit_debit:
        _generate_output(today_s + '_gutschrift', sepa_debit.export(),
                         output_path)
Beispiel #9
0
    def post(self, request, *args, **kwargs):
        self._event_cache = {}

        valid_payments = defaultdict(list)
        files = {}
        for payment in self.get_unexported().select_related(
                'order', 'order__event', 'sepadebit_due'):
            if not payment.info_data:
                # Should not happen
                # TODO: Notify user
                payment.state = OrderPayment.PAYMENT_STATE_FAILED
                payment.save()
                payment.order.status = Order.STATUS_PENDING
                payment.order.save()
                continue

            payment_dict = {
                "name":
                payment.info_data['account'],
                "IBAN":
                payment.info_data['iban'],
                "BIC":
                payment.info_data['bic'],
                "amount":
                int(payment.amount * 100),
                "type":
                "OOFF",
                "collection_date":
                max(now().astimezone(payment.order.event.timezone).date(),
                    payment.sepadebit_due.date),
                "mandate_id":
                payment.info_data['reference'],
                "mandate_date": (payment.order.datetime if payment.migrated
                                 else payment.created).date(),
                "description":
                _('Event ticket {event}-{code}').format(
                    event=payment.order.event.slug.upper(),
                    code=payment.order.code)
            }

            config = self._config_for_event(payment.order.event)
            if config not in files:
                files[config] = SepaDD(dict(config), schema='pain.008.001.02')
            file = files[config]
            file.add_payment(payment_dict)
            valid_payments[file].append(payment)

        if valid_payments:
            with transaction.atomic():
                for k, f in list(files.items()):
                    if hasattr(request, 'event'):
                        exp = SepaExport(event=request.event, xmldata='')
                        exp.testmode = request.event.testmode
                    else:
                        exp = SepaExport(organizer=request.organizer,
                                         xmldata='')
                        exp.testmode = False
                    exp.xmldata = f.export(validate=False).decode('utf-8')

                    import xmlschema  # xmlschema does some weird monkeypatching in etree, if we import it globally, things fail
                    my_schema = xmlschema.XMLSchema(
                        os.path.join(os.path.dirname(validation.__file__),
                                     'schemas', f.schema + '.xsd'))
                    errs = []
                    for e in my_schema.iter_errors(exp.xmldata):
                        errs.append(str(e))
                    if errs:
                        messages.error(
                            request,
                            _('The generated file did not validate for the following reasons. '
                              'Please contact pretix support for more information.\n{}'
                              ).format("\n".join(errs)))
                        del files[k]
                    else:
                        exp.currency = f._config['currency']
                        exp.save()
                        SepaExportOrder.objects.bulk_create([
                            SepaExportOrder(order=p.order,
                                            payment=p,
                                            export=exp,
                                            amount=p.amount)
                            for p in valid_payments[f]
                        ])
            if len(files) > 1:
                messages.warning(
                    request,
                    _('Multiple new export files have been created, since your events '
                      'have differing SEPA settings. Please make sure to process all of them!'
                      ))
            elif len(files) > 0:
                messages.success(request,
                                 _('A new export file has been created.'))
        else:
            messages.warning(request, _('No valid orders have been found.'))
        if hasattr(request, 'event'):
            return redirect(
                reverse('plugins:pretix_sepadebit:export',
                        kwargs={
                            'event': request.event.slug,
                            'organizer': request.organizer.slug,
                        }))
        else:
            return redirect(
                reverse('plugins:pretix_sepadebit:export',
                        kwargs={
                            'organizer': request.organizer.slug,
                        }))
Beispiel #10
0
    def form_valid(self, form):
        config = DirectDebitConfiguration.get_solo()
        global_config = Configuration.get_solo()
        members = self._get_members()
        now_ = now()

        dd_config = {
            "name": form.cleaned_data['own_name'],
            "IBAN": str(form.cleaned_data['own_iban']),
            "BIC": str(form.cleaned_data['own_bic']),
            "batch": True,
            "creditor_id": config.creditor_id,
            "currency": global_config.currency,
            "instrument": "COR1" if form.cleaned_data['cor1'] else "CORE",
        }
        sepa = SepaDD(dd_config,
                      schema=form.cleaned_data['sepa_format'],
                      clean=True)

        exp_member_numbers = [
            ((int(x.split("-")[0]), int(x.split("-")[1])) if "-" in x else
             (int(x), int(x)))
            for x in form.cleaned_data['exp_member_numbers'].split(",")
        ]

        with atomic():
            debit = DirectDebit(
                datetime=now_,
                multiple=True,
                cor1=form.cleaned_data['cor1'],
                pain_descriptor='urn:iso:std:iso:20022:tech:xsd:' +
                form.cleaned_data['sepa_format'],
                additional_data={
                    'login_pk': self.selected_account_login_pk,
                    'account_iban': form.cleaned_data['own_iban'],
                    'account_bic': form.cleaned_data['own_bic'],
                })
            debit_payments = []

            for member in members:
                ## Experimental gates
                if form.cleaned_data['exp_bank_types'] == "DE":
                    if not member.profile_sepa.iban.upper().startswith("DE"):
                        continue
                elif form.cleaned_data['exp_bank_types'] == "NDE":
                    if member.profile_sepa.iban.upper().startswith("DE"):
                        continue

                if exp_member_numbers:
                    if not any(a <= int(member.number) <= b
                               for (a, b) in exp_member_numbers):
                        continue

                debit_payment = DirectDebitPayment(
                    id=uuid4(),
                    type='FRST',  ## FIXME Based on existing data
                    mandate_reference=member.profile_sepa.mandate_reference,
                    collection_date=form.cleaned_data['debit_date'],
                    amount=-member.balance,
                    direct_debit=debit,
                    member=member,
                )

                payment = {
                    "name": member.name,
                    "IBAN": member.profile_sepa.iban,
                    "BIC": member.profile_sepa.bic_autocomplete,
                    "collection_date": debit_payment.collection_date,
                    "amount": int(debit_payment.amount * 100),  # in cents
                    "type": debit_payment.type,
                    "mandate_id": debit_payment.mandate_reference,
                    "mandate_date": member.profile_sepa.issue_date
                    or now_.date(),
                    "description": form.cleaned_data['debit_text'],
                    "endtoend_id": debit_payment.id.hex,
                }
                sepa.add_payment(payment)
                debit_payments.append(debit_payment)

                context = {
                    'creditor_id':
                    config.creditor_id,
                    'sepa_mandate_reference':
                    debit_payment.mandate_reference,
                    'sepa_iban':
                    member.profile_sepa.iban,
                    'sepa_bic':
                    member.profile_sepa.bic_autocomplete,
                    'contact':
                    global_config.mail_from,
                    'association_name':
                    global_config.name,
                    'additional_information':
                    '',
                    'debit_date':
                    form.cleaned_data['debit_date'],
                    'amount':
                    "%.2f %s" % (debit_payment.amount, global_config.currency),
                }

                mail = config.debit_notification_template.to_mail(
                    member.email,
                    context=context,
                    save=False,
                )
                mail.text = form.cleaned_data['text'].format(**context)
                mail.subject = form.cleaned_data['subject'].format(**context)
                mail.save()
                mail.members.add(member)

            debit.sepa_xml = sepa.export(validate=True).decode('utf-8')
            debit.save()
            for p in debit_payments:
                p.save()

        return HttpResponseRedirect(
            reverse('plugins:byro_directdebit:finance.directdebit.transmit_dd',
                    kwargs={'pk': debit.pk}))
Beispiel #11
0
class SepaExport:
    def __init__(self):
        self.invoices = []
        self.config = {
            "name": app.config.get('SEPADD_CREDITOR_NAME'),
            "IBAN": app.config.get('SEPADD_CREDITOR_IBAN'),
            "BIC": app.config.get('SEPADD_CREDITOR_BIC'),
            "batch": app.config.get('SEPADD_BATCH'),
            "creditor_id": app.config.get('SEPADD_CREDITOR_ID'),
            "currency": app.config.get('SEPADD_CURRENCY'),
            "instrument": app.config.get('SEPADD_INSTRUMENT')
        }
        self.sepa = SepaDD(self.config, schema=app.config.get('SEPADD_SCHEMA'))

    def __len__(self):
        return len(self.invoices)

    def add_invoice(self, invoice):
        if (len(invoice.items) == 0):
            # skip invoices without items
            return

        if not invoice.sent:
            raise Exception("Invoice %s has not yet been sent." %
                            invoice.number)
        if invoice.cancelled:
            raise Exception("Invoice %s is cancelled." % invoice.number)
        if not invoice.contact.has_sepa_mandate:
            raise Exception("Invoice %s: No sepa mandate for %s" %
                            (invoice.number, invoice.contact))
        if invoice.payment_type != 'SEPA-DD':
            raise Exception("Invoice %s: Payment Type is not SEPA-DD" %
                            invoice.number)

        if self.sepa.check_payment(self._gen_payment(invoice)):
            self.invoices.append(invoice)

    def _gen_payment(self, invoice):
        if invoice.contact.sepa_mandate_first:
            payment_type = "FRST"
            collection_date = date.today() + timedelta(days=+5)
        else:
            payment_type = "RCUR"
            collection_date = date.today() + timedelta(days=+3)

        payment = {
            "name":
            invoice.contact.name,
            "IBAN":
            invoice.contact.sepa_iban,
            "mandate_id":
            invoice.contact.sepa_mandate_id,
            "mandate_date":
            invoice.contact.sepa_mandate_date,
            "amount":
            int(invoice.amount * 100),
            "type":
            payment_type,  # FRST,RCUR,OOFF,FNAL
            "collection_date":
            collection_date,
            "endtoend_id":
            invoice.exported_id,
            "description":
            "Funkfeuer %s%d %s" %
            (app.config.get('BILLING_REFERENCE_UID_PREFIX'),
             invoice.contact.id, invoice.number)
        }
        return payment

    def add_invoices(self, invoices):
        for invoice in invoices:
            self.add_invoice(invoice)

    @property
    def msg_id(self):
        return self.sepa.msg_id

    def export(self):
        if len(self.invoices) < 1:
            raise Exception("no invoices to export")

        for invoice in self.invoices:
            if invoice.exported_id is None:
                invoice.exported_id = make_id(
                    invoice.number, app.config.get('SEPADD_CREDITOR_NAME'))
                invoice.exported = True

            self.sepa.add_payment(self._gen_payment(invoice))
            if invoice.contact.sepa_mandate_first:
                invoice.contact.sepa_mandate_first = False

        export = self.sepa.export()

        db.session.add(
            model.Job(type='sepa_export',
                      note=self.msg_id,
                      user=current_user,
                      started=datetime.utcnow(),
                      finished=datetime.utcnow()))
        db.session.commit()

        return export
Beispiel #12
0
    def generate_xml_file(self):
        from sepaxml import SepaDD

        sepa_settings = frappe.get_doc("Sepa Direct Debit Settings",
                                       self.company)
        company_iban, company_bic = frappe.db.get_value(
            "Bank Account", sepa_settings.bank_account,
            ["iban", "swift_number"])

        config = {
            "name": sepa_settings.company_name,
            "IBAN": company_iban,
            "BIC": company_bic,
            "batch": self.batch_booking,
            "creditor_id": sepa_settings.
            creditor_identifier,  # supplied by your bank or financial authority
            "currency": self.currency,  # ISO 4217
            "instrument": sepa_settings.instrument  # - default is CORE (B2C)
        }
        sepa = SepaDD(config,
                      schema=sepa_settings.schema or "pain.008.001.02",
                      clean=True)

        for payment_entry in self.payment_entries:
            payment_types = {
                "One-off": "OOFF",
                "First": "FRST",
                "Recurrent": "RCUR",
                "Final": "FNAL"
            }
            payment_type = self.direct_debit_type

            customer = payment_entry.against_account
            if not frappe.db.exists(
                    "Sepa Mandate",
                    dict(customer=customer,
                         registered_on_gocardless=0,
                         status="Active")):
                frappe.throw(
                    _("Please create or activate a SEPA Mandate for customer {0}"
                      .format(customer)))
            else:
                mandate = frappe.get_doc("Sepa Mandate",
                                         dict(customer=customer))
                customer_iban, customer_bic = frappe.db.get_value(
                    "Bank Account", mandate.bank_account,
                    ["iban", "swift_number"])

            pe = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
            sales_invoices = ""

            for ref in pe.references:
                sales_invoices += "/" + ref.reference_name

            payment_amount = cint(payment_entry.amount * 100)

            payment = {
                "name": customer,
                "IBAN": customer_iban,
                "BIC": customer_bic,
                "amount": payment_amount,  # in cents
                "type": payment_types.get(payment_type),  # FRST,RCUR,OOFF,FNAL
                "collection_date": getdate(payment_entry.reference_date),
                "mandate_id": mandate.mandate,
                "mandate_date": mandate.creation_date,
                "description": sepa_settings.reference_prefix + sales_invoices,
                "endtoend_id": pe.reference_no  # autogenerated if obmitted
            }
            sepa.add_payment(payment)
        try:
            sepa_export = sepa.export(
                validate=False)  # TODO: correct false positive upon validation
        except Exception as e:
            frappe.throw(str(e))
        self.save_sepa_export(sepa_export)

        return sepa_export