Exemplo n.º 1
0
    def process_completed_payment(self, trans):
        manager = InvoiceManager()
        invoice = self.get_invoice_for_transaction(trans)

        def invoice_logger(msg):
            raise TrustlyException("Trustly invoice processing failed: {0}".format(msg))

        method = trans.paymentmethod
        pm = method.get_implementation()

        manager.process_incoming_payment_for_invoice(invoice,
                                                     trans.amount,
                                                     'Trustly id {0}'.format(trans.id),
                                                     0,  # XXX: we pay zero now, but should perhaps support fees?
                                                     pm.config('accounting_income'),
                                                     pm.config('accounting_fee'),
                                                     [],
                                                     invoice_logger,
                                                     method)

        TrustlyLog(message="Completed payment for Trustly id {0} (order {1}), {2}{3}, invoice {4}".format(trans.id, trans.orderid, settings.CURRENCY_ABBREV, trans.amount, invoice.id), paymentmethod=method).save()

        send_simple_mail(settings.INVOICE_SENDER_EMAIL,
                         pm.config('notification_receiver'),
                         "Trustly payment completed",
                         "A Trustly payment for {0} of {1}{2} for invoice {3} was completed on the Trustly platform.\n\nInvoice: {4}\nRecipient name: {5}\nRecipient email: {6}\n".format(
                             method.internaldescription,
                             settings.CURRENCY_ABBREV,
                             trans.amount,
                             invoice.id,
                             invoice.title,
                             invoice.recipient_name,
                             invoice.recipient_email),
                         )
    def handle(self, *args, **options):
        manager = InvoiceManager()
        with transaction.atomic():
            for trans in TrustlyTransaction.objects.filter(
                    pendingat__isnull=False, completedat__isnull=True):
                # Pending is set, completed is not set, means we are waiting for a slow transaction.
                try:
                    invoice = Invoice.objects.get(pk=trans.invoiceid)
                except Invoice.DoesNotExist:
                    raise CommandError(
                        "Invoice {0} for order {1} not found!".format(
                            trans.invoiceid, trans.orderid))

                # Make sure the invoice is valid for at least another 24 hours (yay banks that only
                # sync their data once a day)
                # If the invoice is extended, email is sent to invoice admins, but not end users.
                r = manager.postpone_invoice_autocancel(
                    invoice,
                    timedelta(hours=24),
                    "Trustly payment still in pending, awaiting credit",
                    silent=False)
                if r:
                    TrustlyLog(
                        message=
                        "Extended autocancel time for invoice {0} to ensure time for credit notification"
                        .format(invoice.id),
                        paymentmethod=trans.paymentmethod).save()
Exemplo n.º 3
0
def pendinginvoices_cancel(request, urlname, invoiceid):
    conference = get_authenticated_conference(request, urlname)
    invoice = get_object_or_404(Invoice, pk=invoiceid, paidat__isnull=True)

    # Have to verify that this invoice is actually for this conference
    if not (
            ConferenceRegistration.objects.filter(conference=conference, invoice=invoice).exists() or
            BulkPayment.objects.filter(conference=conference, invoice=invoice).exists() or
            Sponsor.objects.filter(conference=conference, invoice=invoice).exists()
    ):
        raise PermissionDenied("Invoice not for this conference")

    if request.method == 'POST':
        form = ConferenceInvoiceCancelForm(data=request.POST)
        if form.is_valid():
            manager = InvoiceManager()
            try:
                manager.cancel_invoice(invoice, form.cleaned_data['reason'], request.user.username)
                messages.info(request, 'Invoice {} canceled.'.format(invoice.id))
                return HttpResponseRedirect('../../')
            except Exception as e:
                messages.error(request, 'Failed to cancel invoice: {}'.format(e))
    else:
        form = ConferenceInvoiceCancelForm()

    return render(request, 'confreg/admin_backend_form.html', {
        'conference': conference,
        'basetemplate': 'confreg/confadmin_base.html',
        'form': form,
        'whatverb': 'Cancel invoice',
        'savebutton': 'Cancel invoice',
        'cancelname': 'Return without canceling',
        'cancelurl': '../../',
        'note': 'Canceling invoice #{} ({}) will disconnect it from the associated objects and send a notification to the recipient of the invoice ({}).'.format(invoice.id, invoice.title, invoice.recipient_name),
    })
Exemplo n.º 4
0
def prepaidorder_refund(request, urlname, orderid):
    conference = get_authenticated_conference(request, urlname)

    order = get_object_or_404(PurchasedVoucher, pk=orderid, conference=conference)

    if PrepaidBatch.objects.filter(pk=order.batch_id).aggregate(used=Count('prepaidvoucher__user'))['used'] > 0:
        # This link should not exist in the first place, but double check if someone
        # used the voucher in between the click.
        messages.error(request, 'Cannot refund order, there are used vouchers in the batch!')
        return HttpResponseRedirect("../../")

    invoice = order.invoice
    if not invoice:
        messages.error(request, 'Order does not have an invoice, there is nothing to refund!')
        return HttpResponseRedirect("../../")
    if not invoice.paidat:
        messages.error(request, 'Invoice for this order has not been paid, there is nothing to refund!')
        return HttpResponseRedirect("../../")

    if request.method == 'POST':
        form = PurchasedVoucherRefundForm(data=request.POST)
        if form.is_valid():
            # Actually issue the refund
            manager = InvoiceManager()
            manager.refund_invoice(invoice, 'Prepaid order refunded', invoice.total_amount - invoice.total_vat, invoice.total_vat, conference.vat_registrations)

            send_conference_notification(
                conference,
                'Prepaid order {} refunded'.format(order.id),
                'Prepaid order {} purchased by {} {} has been refunded.\nNo vouchers were in use, and the order and batch have both been deleted.\n'.format(order.id, order.user.first_name, order.user.last_name),
            )
            order.batch.delete()
            order.delete()

            messages.info(request, 'Order has been refunded and deleted.')
            return HttpResponseRedirect("../../")
    else:
        form = PurchasedVoucherRefundForm()

    if settings.EU_VAT:
        note = 'You are about to refund {}{} ({}{} + {}{} VAT) for invoice {}. Please confirm that this is what you want!'.format(settings.CURRENCY_SYMBOL, invoice.total_amount, settings.CURRENCY_SYMBOL, invoice.total_amount - invoice.total_vat, settings.CURRENCY_SYMBOL, invoice.total_vat, invoice.id)
    else:
        note = 'You are about to refund {}{} for invoice {}. Please confirm that this is what you want!'.format(settings.CURRENCY_SYMBOL, invoice.total_amount, invoice.id)

    return render(request, 'confreg/admin_backend_form.html', {
        'conference': conference,
        'basetemplate': 'confreg/confadmin_base.html',
        'form': form,
        'note': note,
        'whatverb': 'Refund',
        'what': 'repaid vouchers',
        'savebutton': 'Refund',
        'cancelurl': '../../',
        'breadcrumbs': [('/events/admin/{}/prepaidorders/'.format(conference.urlname), 'Prepaid Voucher Orders'), ],
        'helplink': 'vouchers',
    })
Exemplo n.º 5
0
def _flag_invoices(request, trans, invoices, pm, fee_account):
    manager = InvoiceManager()
    invoicelog = []

    transaction.set_autocommit(False)

    def invoicelogger(msg):
        invoicelog.append(msg)

    if len(invoices) == 1:
        fee = invoices[0].total_amount - trans.amount  # Calculated fee
    else:
        # There can be no fees when using multiple invoices, so ensure that
        if sum([i.total_amount for i in invoices]) != trans.amount:
            raise Exception("Fees not supported for multi-invoice flagging")
        fee = 0

    for invoice in invoices:
        (status, _invoice,
         _processor) = manager.process_incoming_payment_for_invoice(
             invoice, invoice.total_amount,
             "Bank transfer from {0} with id {1}, manually matched".format(
                 trans.method.internaldescription,
                 trans.methodidentifier), fee, pm.config('bankaccount'),
             fee_account and fee_account.num, [], invoicelogger, trans.method)

        if status != manager.RESULT_OK:
            messages.error(request, "Failed to run invoice processor:")
            for m in invoicelog:
                messages.warning(request, m)

            # Roll back any changes so far
            transaction.rollback()

            return False

        BankTransferFees(invoice=invoice, fee=fee).save()

        InvoiceLog(
            message=
            "Manually matched invoice {0} for {1} {2}, bank transaction {3} {2}, fees {4}"
            .format(
                invoice.id,
                invoice.total_amount,
                settings.CURRENCY_ABBREV,
                trans.amount,
                fee,
            )).save()

    # Remove the pending transaction
    trans.delete()

    transaction.commit()

    return True
Exemplo n.º 6
0
    def handle(self, *args, **options):
        invoices = Invoice.objects.filter(finalized=True, deleted=False, paidat__isnull=True, canceltime__lt=datetime.now())

        manager = InvoiceManager()

        for invoice in invoices:
            self.stdout.write("Canceling invoice {0}, expired".format(invoice.id))

            # The manager will automatically cancel any registrations etc,
            # as well as send an email to the user.
            manager.cancel_invoice(invoice,
                                   "Invoice was automatically canceled because payment was not received on time.")
Exemplo n.º 7
0
    def handle(self, *args, **options):
        refunds = InvoiceRefund.objects.filter(issued__isnull=True)
        for r in refunds:
            manager = InvoiceManager()

            # One transaction for each object, and make sure it's properly
            # locked by using select for update, in case we get a notification
            # delivered while we are still processing.
            with transaction.atomic():
                rr = InvoiceRefund.objects.select_for_update().filter(
                    pk=r.pk)[0]
                if not rr.invoice.can_autorefund:
                    # How did we end up in the queue?!
                    raise CommandError(
                        "Invoice {0} listed for refund, but provider is not capable of refunds!"
                        .format(r.invoice.id))

                # Calling autorefund will update the InvoiceRefund object
                # after calling the APIs, so nothing more to do here.

                if manager.autorefund_invoice(rr):
                    self.stdout.write(
                        "Issued API refund of invoice {0}.".format(
                            rr.invoice.pk))
                else:
                    self.stdout.write(
                        "Failed to issue API refund for invoice {0}, will keep trying."
                        .format(rr.invoice.pk))

        # Send alerts for any refunds that have been issued but that have not completed within
        # 3 days (completely arbitrary, but normally it happens within seconds/minutes/hours).
        stalledrefunds = InvoiceRefund.objects.filter(
            issued__isnull=False,
            completed__isnull=True,
            issued__lt=datetime.now() - timedelta(days=3))
        if stalledrefunds:
            send_simple_mail(
                settings.INVOICE_SENDER_EMAIL,
                settings.INVOICE_NOTIFICATION_RECEIVER,
                "Stalled invoice refunds",
                """One or more invoice refunds appear to be stalled.
These refunds have been issued to the provider, but no confirmation has
shown up. This requires manual investigation.

The following invoices have stalled refunds:

{0}

Better go check!
""".format("\n".join([r.invoice.invoicestr for r in stalledrefunds])))
Exemplo n.º 8
0
    def process_pending_payment(self, trans):
        # If we have received a 'pending' notification, postpone the invoice to ensure it's valid
        # for another 2 hours, in case the credit notification is slightly delayed.
        # A cronjob will run every hour to potentially further extend this.
        manager = InvoiceManager()
        invoice = self.get_invoice_for_transaction(trans)

        # Postpone the invoice so it's valid for at least another 2 hours.
        r = manager.postpone_invoice_autocancel(invoice,
                                                timedelta(hours=2),
                                                reason="Trustly pending arrived, awaiting credit",
                                                silent=True)
        if r:
            TrustlyLog(message="Extended autocancel time for invoice {0} to ensure time for credit notification".format(invoice.id),
                       paymentmethod=trans.paymentmethod).save()
Exemplo n.º 9
0
def multireg_refund(request, urlname, bulkid):
    conference = get_authenticated_conference(request, urlname)

    bulkpay = get_object_or_404(BulkPayment, pk=bulkid, conference=conference)
    if bulkpay.conferenceregistration_set.exists():
        messages.error(request, "This bulk payment has registrations, cannot be canceled!")
        return HttpResponseRedirect("../../")

    invoice = bulkpay.invoice
    if not invoice:
        messages.error(request, "This bulk payment does not have an invoice!")
        return HttpResonseRedirect("../../")
    if not invoice.paidat:
        messages.error(request, "This bulk payment invoice has not been paid!")
        return HttpResonseRedirect("../../")

    if request.method == 'POST':
        form = BulkPaymentRefundForm(invoice, data=request.POST)
        if form.is_valid():
            manager = InvoiceManager()
            manager.refund_invoice(invoice, 'Multi registration refunded', form.cleaned_data['amount'], form.cleaned_data['vatamount'], conference.vat_registrations)

            send_conference_notification(
                conference,
                'Multi registration {} refunded'.format(bulkpay.id),
                'Multi registration {} purchased by {} {} has been refunded.\nNo registrations were active in this multi registration, and the multi registration has now been deleted.\n'.format(bulkpay.id, bulkpay.user.first_name, bulkpay.user.last_name),
            )
            bulkpay.delete()

            messages.info(request, 'Multi registration has been refunded and deleted.')
            return HttpResponseRedirect("../../")
    else:
        form = BulkPaymentRefundForm(invoice, initial={'amount': invoice.total_amount - invoice.total_vat, 'vatamount': invoice.total_vat})

    return render(request, 'confreg/admin_backend_form.html', {
        'conference': conference,
        'basetemplate': 'confreg/confadmin_base.html',
        'form': form,
        'whatverb': 'Refund',
        'what': 'multi registration',
        'savebutton': 'Refund',
        'cancelurl': '../../',
        'breadcrumbs': [('/events/admin/{}/multiregs/'.format(conference.urlname), 'Multi Registrations'), ],
        'helplink': 'registrations',
    })
Exemplo n.º 10
0
def create_sponsor_invoice(user, sponsor, override_duedate=None):
    conference = sponsor.conference
    level = sponsor.level

    invoicerows, reverse_vat = _invoicerows_for_sponsor(sponsor)

    if override_duedate:
        duedate = override_duedate
    elif conference.startdate < today_conference() + timedelta(days=5):
        # If conference happens in the next 5 days, invoice is due immediately
        duedate = timezone.now()
    elif conference.startdate < today_conference() + timedelta(days=30):
        # Less than 30 days before the conference, set the due date to
        # 5 days before the conference
        duedate = timezone.make_aware(
            datetime.combine(conference.startdate - timedelta(days=5),
                             timezone.now().time()))
    else:
        # More than 30 days before the conference, set the due date
        # to 30 days from now.
        duedate = timezone.now() + timedelta(days=30)

    manager = InvoiceManager()
    processor = invoicemodels.InvoiceProcessor.objects.get(
        processorname="confsponsor processor")
    i = manager.create_invoice(
        user,
        user.email,
        user.first_name + ' ' + user.last_name,
        get_sponsor_invoice_address(sponsor.name, sponsor.invoiceaddr,
                                    sponsor.vatnumber),
        '%s sponsorship' % conference.conferencename,
        timezone.now(),
        duedate,
        invoicerows,
        processor=processor,
        processorid=sponsor.pk,
        accounting_account=settings.ACCOUNTING_CONFSPONSOR_ACCOUNT,
        accounting_object=conference.accounting_object,
        reverse_vat=reverse_vat,
        extra_bcc_list=conference.sponsoraddr,
        paymentmethods=level.paymentmethods.all(),
    )
    return i
Exemplo n.º 11
0
def process_stripe_checkout(co):
    if co.completedat:
        # Already completed, so don't do anything with it
        return

    with transaction.atomic():
        method = co.paymentmethod
        pm = method.get_implementation()
        api = StripeApi(pm)

        # Update the status from the API
        if api.update_checkout_status(co):
            # Went from unpaid to paid, so Do The Magic (TM)
            manager = InvoiceManager()
            invoice = Invoice.objects.get(pk=co.invoiceid)

            def invoice_logger(msg):
                raise StripeException(
                    "Stripe invoice processing failed: {0}".format(msg))

            manager.process_incoming_payment_for_invoice(
                invoice, co.amount, 'Stripe checkout id {0}'.format(co.id),
                co.fee, pm.config('accounting_income'),
                pm.config('accounting_fee'), [], invoice_logger, method)

            StripeLog(
                message=
                "Completed payment for Stripe id {0} ({1}{2}, invoice {3})".
                format(co.id, settings.CURRENCY_ABBREV, co.amount, invoice.id),
                paymentmethod=method).save()

            send_simple_mail(
                settings.INVOICE_SENDER_EMAIL,
                pm.config('notification_receiver'), "Stripe payment completed",
                "A Stripe payment for {0} of {1}{2} for invoice {3} was completed.\n\nInvoice: {4}\nRecipient name: {5}\nRecipient email: {6}\n"
                .format(
                    method.internaldescription,
                    settings.CURRENCY_ABBREV,
                    co.amount,
                    invoice.id,
                    invoice.title,
                    invoice.recipient_name,
                    invoice.recipient_email,
                ))
Exemplo n.º 12
0
def create_voucher_invoice(conference, invoiceaddr, user, rt, num):
    invoicerows = [
        ['Voucher for "%s"' % rt.regtype, 1, rt.cost, rt.conference.vat_registrations]
    ] * num

    manager = InvoiceManager()
    processor = invoicemodels.InvoiceProcessor.objects.get(processorname="confsponsor voucher processor")
    i = manager.create_invoice(
        user,
        user.email,
        user.first_name + ' ' + user.last_name,
        invoiceaddr,
        'Prepaid vouchers for %s' % conference.conferencename,
        datetime.now(),
        date.today(),
        invoicerows,
        processor=processor,
        accounting_account=settings.ACCOUNTING_CONFREG_ACCOUNT,
        accounting_object=conference.accounting_object,
        paymentmethods=conference.paymentmethods.all(),
    )
    return i
Exemplo n.º 13
0
def create_voucher_invoice(sponsor, user, rt, num):
	invoicerows = [
		['Voucher for "%s"' % rt.regtype, num, rt.cost]
		]

	manager = InvoiceManager()
	processor = invoicemodels.InvoiceProcessor.objects.get(processorname="confsponsor voucher processor")
	i = manager.create_invoice(
		user,
		user.email,
		user.first_name + ' ' + user.last_name,
		sponsor.invoiceaddr,
		'Prepaid vouchers for %s' % sponsor.conference.conferencename,
		datetime.now(),
		date.today(),
		invoicerows,
		processor = processor,
		bankinfo = False,
		accounting_account = settings.ACCOUNTING_CONFREG_ACCOUNT,
		accounting_object = sponsor.conference.accounting_object,
		autopaymentoptions = True
	)
	return i
Exemplo n.º 14
0
def create_sponsor_invoice(user, user_name, name, address, conference, level, sponsorid):
	invoicerows = [
		['%s %s sponsorship' % (conference, level), 1, level.levelcost],
	]
	if conference.startdate < date.today() + timedelta(days=5):
		# If conference happens in the next 5 days, invoice is due immediately
		duedate = date.today()
	elif conference.startdate < date.today() + timedelta(days=30):
		# Less than 30 days before the conference, set the due date to
		# 5 days before the conference
		duedate = conference.startdate - timedelta(days=5)
	else:
		# More than 30 days before the conference, set the due date
		# to 30 days from now.
		duedate = datetime.now() + timedelta(days=30)

	manager = InvoiceManager()
	processor = invoicemodels.InvoiceProcessor.objects.get(processorname="confsponsor processor")
	i = manager.create_invoice(
		user,
		user.email,
		user_name,
		'%s\n%s' % (name, address),
		'%s sponsorship' % conference.conferencename,
		datetime.now(),
		duedate,
		invoicerows,
		processor = processor,
		processorid = sponsorid,
		bankinfo = True,
		accounting_account = settings.ACCOUNTING_CONFSPONSOR_ACCOUNT,
		accounting_object = conference.accounting_object,
		autopaymentoptions = False
	)
	i.allowedmethods = level.paymentmethods.all()
	return i
Exemplo n.º 15
0
def process_refund(notification):
    method = notification.rawnotification.paymentmethod
    pm = method.get_implementation()

    # Store the refund, and send an email!
    if notification.success:
        try:
            ts = TransactionStatus.objects.get(
                pspReference=notification.originalReference,
                paymentmethod=method)
            refund = Refund(notification=notification,
                            transaction=ts,
                            refund_amount=notification.amount)
            refund.save()

            urls = [
                "https://ca-live.adyen.com/ca/ca/accounts/showTx.shtml?pspReference=%s&txType=Payment&accountKey=MerchantAccount.%s"
                %
                (notification.pspReference, notification.merchantAccountCode),
            ]

            # API generated refund?
            if notification.merchantReference.startswith(
                    pm.config('merchantref_refund_prefix')):
                # API generated
                invoicerefundid = int(notification.merchantReference[
                    len(pm.config('merchantref_refund_prefix')):])

                manager = InvoiceManager()
                manager.complete_refund(
                    invoicerefundid,
                    refund.refund_amount,
                    0,  # we don't know the fee, it'll be generically booked
                    pm.config('accounting_refunds'),
                    pm.config('accounting_fee'),
                    urls,
                    method)
            else:
                # Generate an open accounting record for this refund.
                # We expect this happens so seldom that we can just deal with
                # manually finishing off the accounting records.

                accrows = [
                    (pm.config('accounting_refunds'),
                     "Refund of %s (transaction %s) " %
                     (ts.notes, ts.pspReference), -refund.refund_amount, None),
                ]

                send_simple_mail(
                    settings.INVOICE_SENDER_EMAIL,
                    pm.config('notification_receiver'),
                    'Adyen refund received',
                    "A refund of %s%s for transaction %s was processed on %s\n\nNOTE! You must complete the accounting system entry manually as it was not API generated!!"
                    % (settings.CURRENCY_ABBREV, notification.amount,
                       method.internaldescription,
                       notification.originalReference))

                create_accounting_entry(date.today(), accrows, True, urls)

        except TransactionStatus.DoesNotExist:
            send_simple_mail(
                settings.INVOICE_SENDER_EMAIL,
                pm.config('notification_receiver'),
                'Adyen refund received for nonexisting transaction',
                "A refund for %s was received on %s, but the transaction does not exist!\n\nYou probably want to investigate this!\n"
                % (notification.originalReference, method.internaldescription))
    else:
        send_simple_mail(
            settings.INVOICE_SENDER_EMAIL, pm.config('notification_receiver'),
            'Unsuccessful adyen refund received',
            "A refund for %s has failed on %s.\nThe reason given was:\n%s\n\nYou probably want to investigate this!\n"
            % (notification.merchantReference, method.internaldescription,
               notification.reason))
    notification.confirmed = True
    notification.save()
Exemplo n.º 16
0
			ti.save()

			# Generate a simple accounting record, that will have to be
			# manually completed.
			accstr = "Paypal donation %s" % ti.paypaltransid
			accrows = [
				(settings.ACCOUNTING_PAYPAL_INCOME_ACCOUNT, accstr, ti.amount-ti.fee, None),
				(settings.ACCOUNTING_PAYPAL_FEE_ACCOUNT, accstr, ti.fee, None),
				(settings.ACCOUNTING_DONATIONS_ACCOUNT, accstr, -ti.amount, None),
				]
			create_accounting_entry(date.today(), accrows, True, urls)

			return render_to_response('paypal/noinvoice.html', {
					}, context_instance=RequestContext(request))

		invoicemanager = InvoiceManager()
		(r,i,p) = invoicemanager.process_incoming_payment(ti.transtext,
														  ti.amount,
														  "Paypal id %s, from %s <%s>, auto" % (ti.paypaltransid, ti.sendername, ti.sender),
														  ti.fee,
														  settings.ACCOUNTING_PAYPAL_INCOME_ACCOUNT,
														  settings.ACCOUNTING_PAYPAL_FEE_ACCOUNT,
														  urls,
														  payment_logger)
		if r == invoicemanager.RESULT_OK:
			# Matched it!
			ti.matched = True
			ti.matchinfo = 'Matched standard invoice (auto)'
			ti.save()

			# Now figure out where to return the user. This comes from the
Exemplo n.º 17
0
    def handle_method(self, method):
        pm = method.get_implementation()

        api = pm.get_api()

        for t in api.get_transactions():
            # We will re-fetch most transactions, so only create them if they are not
            # already there.
            trans, created = TransferwiseTransaction.objects.get_or_create(
                paymentmethod=method,
                twreference=t['referenceNumber'],
                defaults={
                    'datetime': api.parse_datetime(t['date']),
                    'amount': api.get_structured_amount(t['amount']),
                    'feeamount': api.get_structured_amount(t['totalFees']),
                    'transtype': t['details']['type'],
                    'paymentref': t['details']['paymentReference'][:200],
                    'fulldescription': t['details']['description'],
                })
            if created:
                # Set optional fields
                trans.counterpart_name = t['details'].get('senderName', '')
                trans.counterpart_account = t['details'].get(
                    'senderAccount', '').replace(' ', '')

                # Weird stuff that sometimes shows up
                if trans.counterpart_account == 'Unknownbankaccount':
                    trans.counterpart_account = ''

                if trans.counterpart_account:
                    # If account is IBAN, then try to validate it!
                    trans.counterpart_valid_iban = api.validate_iban(
                        trans.counterpart_account)
                trans.save()

                # If this is a refund transaction, process it as such
                if trans.transtype == 'TRANSFER' and trans.paymentref.startswith(
                        '{0} refund'.format(settings.ORG_SHORTNAME)):
                    # Yes, this is one of our refunds. Can we find the corresponding transaction?
                    m = re.match(r'^TRANSFER-(\d+)$', t['referenceNumber'])
                    if not m:
                        raise Exception(
                            "Could not find TRANSFER info in transfer reference {0}"
                            .format(t['referenceNumber']))
                    transferid = m.groups(1)[0]
                    try:
                        twrefund = TransferwiseRefund.objects.get(
                            transferid=transferid)
                    except TransferwiseRefund.DoesNotExist:
                        print(
                            "Could not find transferwise refund for id {0}, registering as manual bank transaction"
                            .format(transferid))
                        register_bank_transaction(method, trans.id,
                                                  trans.amount,
                                                  trans.paymentref,
                                                  trans.fulldescription, False)
                        continue

                    if twrefund.refundtransaction or twrefund.completedat:
                        raise Exception(
                            "Transferwise refund for id {0} has already been processed!"
                            .format(transferid))

                    # Flag this one as done!
                    twrefund.refundtransaction = trans
                    twrefund.completedat = datetime.now()
                    twrefund.save()

                    invoicemanager = InvoiceManager()
                    invoicemanager.complete_refund(
                        twrefund.refundid,
                        trans.amount + trans.feeamount,
                        trans.feeamount,
                        pm.config('bankaccount'),
                        pm.config('feeaccount'),
                        [],  # urls
                        method,
                    )
                elif trans.transtype == 'TRANSFER' and trans.paymentref.startswith(
                        '{0} returned payment'.format(settings.ORG_SHORTNAME)):
                    # Returned payment. Nothing much to do, but we create an accounting record
                    # for it just to make things nice and clear. But first make sure we can
                    # actually find the original transaction.
                    try:
                        po = TransferwisePayout.objects.get(
                            reference=trans.paymentref)
                    except TransferwisePayout.DoesNotExist:
                        raise Exception(
                            "Could not find transferwise payout object for {0}"
                            .format(trans.paymentref))

                    po.completedat = datetime.now()
                    po.completedtrans = trans
                    po.save()

                    m = re.match(
                        r'^{0} returned payment (\d+)$'.format(
                            settings.ORG_SHORTNAME), trans.paymentref)
                    if not m:
                        raise Exception(
                            "Could not find returned transaction id in reference '{0}'"
                            .format(trans.paymentref))
                    twtrans = TransferwiseTransaction.objects.get(
                        pk=m.groups(1)[0])
                    if twtrans.amount != -trans.amount - trans.feeamount:
                        raise Exception(
                            "Original amount {0} does not match returned amount {1}"
                            .format(twtrans.amount,
                                    -trans.amount - trans.feeamount))

                    accountingtxt = "TransferWise returned payment {0}".format(
                        trans.twreference)
                    accrows = [
                        (pm.config('bankaccount'), accountingtxt, trans.amount,
                         None),
                        (pm.config('feeaccount'), accountingtxt,
                         trans.feeamount, None),
                        (pm.config('bankaccount'), accountingtxt,
                         -(trans.amount + trans.feeamount), None),
                    ]
                    create_accounting_entry(date.today(), accrows)
                elif trans.transtype == 'TRANSFER' and trans.paymentref.startswith(
                        'TW payout'):
                    # Payout. Create an appropriate accounting record and a pending matcher.
                    try:
                        po = TransferwisePayout.objects.get(
                            reference=trans.paymentref)
                    except TransferwisePayout.DoesNotExist:
                        raise Exception(
                            "Could not find transferwise payout object for {0}"
                            .format(trans.paymentref))

                    refno = int(trans.paymentref[len("TW payout "):])

                    if po.amount != -(trans.amount + trans.feeamount):
                        raise Exception(
                            "Transferwise payout {0} returned transaction with amount {1} instead of {2}"
                            .format(refno, -(trans.amount + trans.feeamount),
                                    po.amount))

                    po.completedat = datetime.now()
                    po.completedtrans = trans
                    po.save()

                    # Payout exists at TW, so proceed to generate records. If the receiving account
                    # is a managed one, create a bank matcher. Otherwise just close the record
                    # immediately.
                    accrows = [
                        (pm.config('bankaccount'), trans.paymentref,
                         trans.amount, None),
                        (pm.config('feeaccount'), trans.paymentref,
                         trans.feeamount, None),
                        (pm.config('accounting_payout'), trans.paymentref,
                         -(trans.amount + trans.feeamount), None),
                    ]
                    if is_managed_bank_account(pm.config('accounting_payout')):
                        entry = create_accounting_entry(
                            date.today(), accrows, True)
                        register_pending_bank_matcher(
                            pm.config('accounting_payout'),
                            '.*TW.*payout.*{0}.*'.format(refno),
                            -(trans.amount + trans.feeamount), entry)
                    else:
                        create_accounting_entry(date.today(), accrows)
                else:
                    # Else register a pending bank transaction. This may immediately match an invoice
                    # if it was an invoice payment, in which case the entire process will copmlete.
                    register_bank_transaction(method, trans.id, trans.amount,
                                              trans.paymentref,
                                              trans.fulldescription,
                                              trans.counterpart_valid_iban)
Exemplo n.º 18
0
def _invoice_payment(request, methodid, invoice, trailer):
    method = get_object_or_404(InvoicePaymentMethod, active=True, pk=methodid)
    pm = method.get_implementation()

    if trailer == 'return/':
        # This is a payment return URL, so we wait for the status to be posted.
        if invoice.ispaid:
            # Success, this invoice is paid!
            return HttpResponseRedirect(InvoiceManager().get_invoice_return_url(invoice))

        # Else we wait for it to be. Return the pending page which will auto-refresh itself.
        # We sneakily use the "pspReference" field and put the invoice id in it, because that will never
        # conflict with an actual Adyen pspReference.
        status, created = ReturnAuthorizationStatus.objects.get_or_create(pspReference='INVOICE{}'.format(invoice.id))
        status.seencount += 1
        status.save()
        return render(request, 'adyen/authorized.html', {
            'refresh': 3**status.seencount,
            'returnurl': InvoiceManager().get_invoice_return_url(invoice),
        })

    if trailer == 'iban/':
        methods = ['bankTransfer_IBAN']
    else:
        methods = ['card']

    # Not the return handler, so use the Adyen checkout API to build a payment link.
    p = {
        'reference': '{}{}'.format(pm.config('merchantref_prefix'), invoice.id),
        'amount': {
            'value': int(invoice.total_amount * Decimal(100.0)),
            'currency': 'EUR',
        },
        'description': invoice.invoicestr,
        'merchantAccount': pm.config('merchantaccount'),
        'allowedPaymentMethods': methods,
        'returnUrl': '{}/invoices/adyenpayment/{}/{}/{}/return/'.format(settings.SITEBASE, methodid, invoice.id, invoice.recipient_secret),
    }

    try:
        r = requests.post(
            '{}/v68/paymentLinks'.format(pm.config('checkoutbaseurl').rstrip('/')),
            json=p,
            headers={
                'x-api-key': pm.config('ws_apikey'),
            },
            timeout=10,
        )
        if r.status_code != 201:
            AdyenLog(pspReference='', message='Status code {} when trying to create a payment link. Response: {}'.format(r.status_code, r.text), error=True, paymentmethod=method).save()
            return HttpResponse('Failed to create payment link. Please try again later.')

        j = r.json()

        AdyenLog(pspReference='', message='Created payment link {} for invoice {}'.format(j['id'], invoice.id), error=False, paymentmethod=method).save()

        # Then redirect the user to the payment link we received
        return HttpResponseRedirect(j['url'])
    except requests.exceptions.ReadTimeout:
        AdyenLog(pspReference='', message='timeout when trying to create a payment link', error=True, paymentmethod=method).save()
        return HttpResponse('Failed to create payment link. Please try again later.')
    except Exception as e:
        AdyenLog(pspReference='', message='Exception when trying to create a payment link:{}'.format(e), error=True, paymentmethod=method).save()
        return HttpResponse('Failed to create payment link. Please try again later.')
Exemplo n.º 19
0
def paypal_return_handler(request, methodid):
    tx = 'UNKNOWN'

    method = get_object_or_404(InvoicePaymentMethod,
                               pk=int(methodid),
                               active=True)
    pm = method.get_implementation()

    # Custom error return that can get to the request context
    def paypal_error(reason):
        return render(request, 'paypal/error.html', {
            'reason': reason,
        })

    # Logger for the invoice processing - we store it in the genereal
    # paypal logs
    def payment_logger(msg):
        ErrorLog(
            timestamp=timezone.now(),
            sent=False,
            message='Paypal automatch for %s: %s' % (tx, msg),
            paymentmethod=method,
        ).save()

    # Now for the main handler

    # Handle a paypal PDT return
    if 'tx' not in request.GET:
        return paypal_error('Transaction id not received from paypal')

    tx = request.GET['tx']
    # We have a transaction id. First we check if we already have it
    # in the database.
    # We only store transactions with status paid, so if it's in there,
    # then it's already paid, and what's happening here is a replay
    # (either by mistake or intentional). So we don't redirect the user
    # at this point, we just give an error message.
    try:
        ti = TransactionInfo.objects.get(paypaltransid=tx)
        return HttpResponseForbidden(
            'This transaction has already been processed')
    except TransactionInfo.DoesNotExist:
        pass

    # We haven't stored the status of this transaction. It either means
    # this is the first load, or that we have only seen pending state on
    # it before. Thus, we need to post back to paypal to figure out the
    # current status.
    try:
        params = {
            'cmd': '_notify-synch',
            'tx': tx,
            'at': pm.config('pdt_token'),
        }
        resp = requests.post(pm.get_baseurl(), data=params)
        if resp.status_code != 200:
            raise Exception("status code {0}".format(resp.status_code))
        r = resp.text
    except Exception as ex:
        # Failed to talk to paypal somehow. It should be ok to retry.
        return paypal_error('Failed to verify status with paypal: %s' % ex)

    # First line of paypal response contains SUCCESS if we got a valid
    # response (which might *not* mean it's actually a payment!)
    lines = r.split("\n")
    if lines[0] != 'SUCCESS':
        return paypal_error('Received an error from paypal.')

    # Drop the SUCCESS line
    lines = lines[1:]

    # The rest of the response is urlencoded key/value pairs
    d = dict([unquote_plus(line).split('=') for line in lines if line != ''])

    # Validate things that should never be wrong
    try:
        if d['txn_id'] != tx:
            return paypal_error('Received invalid transaction id from paypal')
        if d['txn_type'] != 'web_accept':
            return paypal_error(
                'Received transaction type %s which is unknown by this system!'
                % d['txn_type'])
        if d['business'] != pm.config('email'):
            return paypal_error(
                'Received payment for %s which is not the correct recipient!' %
                d['business'])
        if d['mc_currency'] != settings.CURRENCY_ABBREV:
            return paypal_error(
                'Received payment in %s, not %s. We cannot currently process this automatically.'
                % (d['mc_currency'], settings.CURRENCY_ABBREV))
    except KeyError as k:
        return paypal_error('Mandatory field %s is missing from paypal data!',
                            k)

    # Now let's find the state of the payment
    if 'payment_status' not in d:
        return paypal_error('Payment status not received from paypal!')

    if d['payment_status'] == 'Completed':
        # Payment is completed. Create a paypal transaction info
        # object for it, and then try to match it to an invoice.

        # Double-check if it is already added. We did check this furter
        # up, but it seems it can sometimes be called more than once
        # asynchronously, due to the check with paypal taking too
        # long.
        if TransactionInfo.objects.filter(paypaltransid=tx).exists():
            return HttpResponse("Transaction already processed",
                                content_type='text/plain')

        # Paypal seems to randomly change which field actually contains
        # the transaction title.
        if d.get('transaction_subject', ''):
            transtext = d['transaction_subject']
        else:
            transtext = d['item_name']
        ti = TransactionInfo(paypaltransid=tx,
                             timestamp=timezone.now(),
                             paymentmethod=method,
                             sender=d['payer_email'],
                             sendername=d['first_name'] + ' ' + d['last_name'],
                             amount=Decimal(d['mc_gross']),
                             fee=Decimal(d['mc_fee']),
                             transtext=transtext,
                             matched=False)
        ti.save()

        # Generate URLs that link back to paypal in a way that we can use
        # from the accounting system. Note that this is an undocumented
        # URL format for paypal, so it may stop working at some point in
        # the future.
        urls = [
            "%s?cmd=_view-a-trans&id=%s" % (
                pm.get_baseurl(),
                ti.paypaltransid,
            ),
        ]

        # Separate out donations made through our website
        if ti.transtext == pm.config('donation_text'):
            ti.matched = True
            ti.matchinfo = 'Donation, automatically matched'
            ti.save()

            # Generate a simple accounting record, that will have to be
            # manually completed.
            accstr = "Paypal donation %s" % ti.paypaltransid
            accrows = [
                (pm.config('accounting_income'), accstr, ti.amount - ti.fee,
                 None),
                (pm.config('accounting_fee'), accstr, ti.fee, None),
                (settings.ACCOUNTING_DONATIONS_ACCOUNT, accstr, -ti.amount,
                 None),
            ]
            create_accounting_entry(accrows, True, urls)

            return render(request, 'paypal/noinvoice.html', {})

        invoicemanager = InvoiceManager()
        (r, i, p) = invoicemanager.process_incoming_payment(
            ti.transtext,
            ti.amount,
            "Paypal id %s, from %s <%s>, auto" %
            (ti.paypaltransid, ti.sendername, ti.sender),
            ti.fee,
            pm.config('accounting_income'),
            pm.config('accounting_fee'),
            urls,
            payment_logger,
            method,
        )
        if r == invoicemanager.RESULT_OK:
            # Matched it!
            ti.matched = True
            ti.matchinfo = 'Matched standard invoice (auto)'
            ti.save()

            # Now figure out where to return the user. This comes from the
            # invoice processor, assuming we have one
            if p:
                url = p.get_return_url(i)
            else:
                # No processor, so redirect the user back to the basic
                # invoice page.
                if i.recipient_user:
                    # Registered to a specific user, so request that users
                    # login on redirect
                    url = "%s/invoices/%s/" % (settings.SITEBASE, i.pk)
                else:
                    # No user account registered, so send back to the secret
                    # url version
                    url = "%s/invoices/%s/%s/" % (settings.SITEBASE, i.pk,
                                                  i.recipient_secret)

            return render(request, 'paypal/complete.html', {
                'invoice': i,
                'url': url,
            })
        else:
            # Did not match an invoice anywhere!
            # We'll leave the transaction in the paypal transaction
            # list, where it will generate an alert in the nightly mail.
            return render(request, 'paypal/noinvoice.html', {})

    # For a pending payment, we set ourselves up with a redirect loop
    if d['payment_status'] == 'Pending':
        try:
            pending_reason = d['pending_reason']
        except Exception as e:
            pending_reason = 'no reason given'
        return render(request, 'paypal/pending.html', {
            'reason': pending_reason,
        })
    return paypal_error('Unknown payment status %s.' % d['payment_status'])
Exemplo n.º 20
0
    def handle(self, *args, **options):
        invoicemanager = InvoiceManager()

        # There may be multiple accounts, so loop over them
        for method in InvoicePaymentMethod.objects.filter(
                active=True,
                classname='postgresqleu.util.payment.paypal.Paypal'):
            pm = method.get_implementation()

            translist = TransactionInfo.objects.filter(
                matched=False, paymentmethod=method).order_by('timestamp')

            for trans in translist:
                # URLs for linkback to paypal
                urls = [
                    "%s?cmd=_view-a-trans&id=%s" % (
                        pm.get_baseurl(),
                        trans.paypaltransid,
                    ),
                ]

                # Manual handling of some record types

                # Record type: donation
                if trans.transtext == pm.config('donation_text'):
                    trans.setmatched(
                        'Donation, automatically matched by script')

                    # Generate a simple accounting record, that will have to be
                    # manually completed.
                    accstr = "Paypal donation %s" % trans.paypaltransid
                    accrows = [
                        (pm.config('accounting_income'), accstr,
                         trans.amount - trans.fee, None),
                        (pm.config('accounting_fee'), accstr, trans.fee, None),
                        (settings.ACCOUNTING_DONATIONS_ACCOUNT, accstr,
                         -trans.amount, None),
                    ]
                    create_accounting_entry(trans.timestamp.date(), accrows,
                                            True, urls)
                    continue
                # Record type: payment, but with no notice (auto-generated elsewhere, the text is
                # hard-coded in paypal_fetch.py
                if trans.transtext == "Paypal payment with empty note" or trans.transtext == "Recurring paypal payment without note":
                    trans.setmatched(
                        'Empty payment description, leaving for operator')

                    accstr = "Unlabeled paypal payment from {0}".format(
                        trans.sender)
                    accrows = [
                        (pm.config('accounting_income'), accstr,
                         trans.amount - trans.fee, None),
                    ]
                    if trans.fee:
                        accrows.append((pm.config('accounting_fee'), accstr,
                                        trans.fee, None), )
                    create_accounting_entry(trans.timestamp.date(), accrows,
                                            True, urls)
                    continue
                # Record type: transfer
                if trans.amount < 0 and trans.transtext == 'Transfer from Paypal to bank':
                    trans.setmatched(
                        'Bank transfer, automatically matched by script')
                    # There are no fees on the transfer, and the amount is already
                    # "reversed" and will automatically become a credit entry.
                    accstr = 'Transfer from Paypal to bank'
                    accrows = [
                        (pm.config('accounting_income'), accstr, trans.amount,
                         None),
                        (pm.config('accounting_transfer'), accstr,
                         -trans.amount, None),
                    ]
                    entry = create_accounting_entry(trans.timestamp.date(),
                                                    accrows, True, urls)
                    if is_managed_bank_account(
                            pm.config('accounting_transfer')):
                        register_pending_bank_matcher(
                            pm.config('accounting_transfer'), '.*PAYPAL.*',
                            -trans.amount, entry)
                    continue
                textstart = 'Refund of Paypal payment: {0} refund '.format(
                    settings.ORG_SHORTNAME)
                if trans.amount < 0 and trans.transtext.startswith(textstart):
                    trans.setmatched('Matched API initiated refund')
                    # API initiated refund, so we should be able to match it
                    invoicemanager.complete_refund(
                        trans.transtext[len(textstart):],
                        -trans.amount,
                        -trans.fee,
                        pm.config('accounting_income'),
                        pm.config('accounting_fee'),
                        urls,
                        method,
                    )

                    # Accounting record is created by invoice manager
                    continue
                # Record type: outgoing payment (or manual refund)
                if trans.amount < 0:
                    trans.setmatched(
                        'Outgoing payment or manual refund, automatically matched by script'
                    )
                    # Refunds typically have a fee (a reversed fee), whereas pure
                    # payments don't have one. We don't make a difference of them
                    # though - we leave the record open for manual verification
                    accrows = [
                        (pm.config('accounting_income'), trans.transtext[:200],
                         trans.amount - trans.fee, None),
                    ]
                    if trans.fee != 0:
                        accrows.append(
                            (pm.config('accounting_fee'),
                             trans.transtext[:200], trans.fee, None), )
                    create_accounting_entry(trans.timestamp.date(), accrows,
                                            True, urls)
                    continue

                # Otherwise, it's an incoming payment. In this case, we try to
                # match it to an invoice.

                # Log things to the db
                def payment_logger(msg):
                    # Write the log output to somewhere interesting!
                    ErrorLog(
                        timestamp=datetime.now(),
                        sent=False,
                        message='Paypal %s by %s (%s) on %s: %s' %
                        (trans.paypaltransid, trans.sender, trans.sendername,
                         trans.timestamp, msg),
                        paymentmethod=method,
                    ).save()

                (r, i, p) = invoicemanager.process_incoming_payment(
                    trans.transtext,
                    trans.amount,
                    "Paypal id %s, from %s <%s>" %
                    (trans.paypaltransid, trans.sendername, trans.sender),
                    trans.fee,
                    pm.config('accounting_income'),
                    pm.config('accounting_fee'),
                    urls,
                    payment_logger,
                    method,
                )

                if r == invoicemanager.RESULT_OK:
                    trans.setmatched('Matched standard invoice')
                elif r == invoicemanager.RESULT_NOTMATCHED:
                    # Could not match this to our pattern of transaction texts. Treat it
                    # the same way as we would treat an empty one -- create an open accounting
                    # entry.
                    trans.setmatched(
                        'Could not match transaction text, leaving for operator'
                    )

                    accstr = "Paypal payment '{0}' from {1}".format(
                        trans.transtext, trans.sender)
                    accrows = [
                        (pm.config('accounting_income'), accstr,
                         trans.amount - trans.fee, None),
                    ]
                    if trans.fee:
                        accrows.append((pm.config('accounting_fee'), accstr,
                                        trans.fee, None), )
                    create_accounting_entry(trans.timestamp.date(), accrows,
                                            True, urls)
                else:
                    # Logging is done by the invoice manager callback
                    pass
Exemplo n.º 21
0
def run():
	invoicemanager = InvoiceManager()

	translist = TransactionInfo.objects.filter(matched=False).order_by('timestamp')

	for trans in translist:
		# URLs for linkback to paypal
		urls = ["https://www.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s" % trans.paypaltransid,]

		# Manual handling of some record types

		# Record type: donation
		if trans.transtext == "PostgreSQL Europe donation":
			trans.setmatched('Donation, automatically matched by script')

			# Generate a simple accounting record, that will have to be
			# manually completed.
			accstr = "Paypal donation %s" % trans.paypaltransid
			accrows = [
				(settings.ACCOUNTING_PAYPAL_INCOME_ACCOUNT, accstr, trans.amount-trans.fee, None),
				(settings.ACCOUNTING_PAYPAL_FEE_ACCOUNT, accstr, trans.fee, None),
				(settings.ACCOUNTING_DONATIONS_ACCOUNT, accstr, -trans.amount, None),
				]
			create_accounting_entry(trans.timestamp.date(), accrows, True, urls)
			continue
		# Record type: transfer
		if trans.amount < 0 and trans.transtext == 'Transfer from Paypal to bank':
			trans.setmatched('Bank transfer, automatically matched by script')
			# There are no fees on the transfer, and the amount is already
			# "reversed" and will automatically become a credit entry.
			accstr = 'Transfer from Paypal to bank'
			accrows = [
				(settings.ACCOUNTING_PAYPAL_INCOME_ACCOUNT, accstr, trans.amount, None),
				(settings.ACCOUNTING_PAYPAL_TRANSFER_ACCOUNT, accstr, -trans.amount, None),
				]
			create_accounting_entry(trans.timestamp.date(), accrows, True, urls)
			continue
		# Record type: payment (or refund)
		if trans.amount < 0:
			trans.setmatched('Payment or refund, automatically matched by script')
			# Refunds typically have a fee (a reversed fee), whereas pure
			# payments don't have one. We don't make a difference of them
			# though - we lave the record open for manual verification
			accrows = [
				(settings.ACCOUNTING_PAYPAL_INCOME_ACCOUNT, trans.transtext, trans.amount - trans.fee, None),
			]
			if trans.fee <> 0:
				accrows.append((settings.ACCOUNTING_PAYPAL_FEE_ACCOUNT, trans.transtext, trans.fee, None),)
			create_accounting_entry(trans.timestamp.date(), accrows, True, urls)
			continue

		# Otherwise, it's an incoming payment. In this case, we try to
		# match it to an invoice.

		# Log things to the db
		def payment_logger(msg):
			# Write the log output to somewhere interesting!
			ErrorLog(timestamp=datetime.now(),
					 sent=False,
					 message='Paypal %s by %s (%s) on %s: %s' % (
					trans.paypaltransid,
					trans.sender,
					trans.sendername,
					trans.timestamp,
					msg
					)).save()

		(r,i,p) = invoicemanager.process_incoming_payment(trans.transtext,
														  trans.amount,
														  "Paypal id %s, from %s <%s>" % (trans.paypaltransid, trans.sendername, trans.sender),
														  trans.fee,
														  settings.ACCOUNTING_PAYPAL_INCOME_ACCOUNT,
														  settings.ACCOUNTING_PAYPAL_FEE_ACCOUNT,
														  urls,
														  payment_logger)

		if r == invoicemanager.RESULT_OK:
			trans.setmatched('Matched standard invoice')
		else:
			# Logging is done by the invoice manager callback
			pass
Exemplo n.º 22
0
    def handle(self, *args, **options):
        # We're always going to process all conferences, since most will not have any
        # open discount codes.
        filt = Q(sponsor__isnull=False, is_invoiced=False) & (
            Q(validuntil__lte=today_global()) | Q(num_uses__gte=F('maxuses')))
        codes = DiscountCode.objects.annotate(
            num_uses=Count('registrations')).filter(filt)
        for code in codes:
            # Either the code has expired, or it is fully used by now. Time to generate the invoice. We'll also
            # send an email to the sponsor (and the admins) to inform them of what's happening.
            # The invoice will be a one-off one, we don't need a registered manager for it since the
            # discounts have already been given out.

            if code.count == 0:
                # In case there is not a single user, we just notify the user of this and set it to
                # invoiced in the system so we don't try again.
                code.is_invoiced = True
                code.save()
                send_conference_sponsor_notification(
                    code.conference,
                    "[{0}] Discount code expired".format(code.conference),
                    "Discount code {0} has expired without any uses.".format(
                        code.code),
                )

                for manager in code.sponsor.managers.all():
                    send_conference_mail(
                        code.conference,
                        manager.email,
                        "Discount code {0} expired".format(code.code),
                        'confsponsor/mail/discount_expired.txt', {
                            'code': code,
                            'sponsor': code.sponsor,
                            'conference': code.conference,
                        },
                        sender=code.conference.sponsoraddr,
                        receivername='{0} {1}'.format(manager.first_name,
                                                      manager.last_name))
            else:
                # At least one use, so we generate the invoice
                invoicerows = []
                for r in code.registrations.all():
                    if code.discountamount:
                        # Fixed amount discount. Always apply
                        discountvalue = code.discountamount
                    else:
                        # Percentage discount, so we need to calculate it. Ordered discount codes will
                        # only support a registration-only style discount code, so only count it
                        # against that.
                        discountvalue = r.regtype.cost * code.discountpercentage / 100
                    invoicerows.append([
                        'Attendee "{0}"'.format(r.fullname), 1, discountvalue,
                        r.conference.vat_registrations
                    ])
                # All invoices are always due immediately
                manager = InvoiceManager()
                code.invoice = manager.create_invoice(
                    code.sponsor_rep,
                    code.sponsor_rep.email,
                    "{0} {1}".format(code.sponsor_rep.first_name,
                                     code.sponsor_rep.last_name),
                    '%s\n%s' % (code.sponsor.name, code.sponsor.invoiceaddr),
                    '{0} discount code {1}'.format(code.conference, code.code),
                    timezone.now(),
                    timezone.now() + timedelta(days=1),
                    invoicerows,
                    accounting_account=settings.ACCOUNTING_CONFREG_ACCOUNT,
                    accounting_object=code.conference.accounting_object,
                    paymentmethods=code.conference.paymentmethods.all(),
                )
                code.invoice.save()
                code.is_invoiced = True
                code.save()

                wrapper = InvoiceWrapper(code.invoice)
                wrapper.email_invoice()

                # Now also fire off emails, both to the admins and to all the managers of the sponsor
                # (so they know where the invoice was sent).
                send_conference_sponsor_notification(
                    code.conference,
                    "[{0}] Discount code {1} has been invoiced".format(
                        code.conference, code.code),
                    "The discount code {0} has been closed,\nand an invoice has been sent to {1}.\n\nA total of {2} registrations used this code, and the total amount was {3}.\n"
                    .format(
                        code.code,
                        code.sponsor,
                        len(invoicerows),
                        code.invoice.total_amount,
                    ),
                )

                for manager in code.sponsor.managers.all():
                    send_conference_mail(
                        code.conference,
                        manager.email,
                        "Discount code {0} has been invoiced".format(
                            code.code),
                        'confsponsor/mail/discount_invoiced.txt', {
                            'code': code,
                            'conference': code.conference,
                            'sponsor': code.sponsor,
                            'invoice': code.invoice,
                            'curr': settings.CURRENCY_ABBREV,
                            'expired_time': code.validuntil < today_global(),
                        },
                        sender=code.conference.sponsoraddr,
                        receivername='{0} {1}'.format(manager.first_name,
                                                      manager.last_name))
Exemplo n.º 23
0
def home(request):
    try:
        member = Member.objects.get(user=request.user)
        registration_complete = True

        # We have a batch job that expires members, but do it here as well to make sure
        # the web is up to date with information if necessary.
        if member.paiduntil and member.paiduntil < today_global():
            MemberLog(member=member,
                      timestamp=timezone.now(),
                      message="Membership expired").save()
            member.membersince = None
            member.paiduntil = None
            member.save()

    except Member.DoesNotExist:
        # No record yet, so we create one. Base the information on whatever we
        # have already.
        member = Member(user=request.user, fullname="{0} {1}".format(request.user.first_name, request.user.last_name))
        registration_complete = False

    cfg = get_config()

    if request.method == "POST":
        if request.POST["submit"] == "Generate invoice":
            # Generate an invoice for the user
            if member.activeinvoice:
                raise Exception("This should not happen - generating invoice when one already exists!")

            manager = InvoiceManager()
            processor = InvoiceProcessor.objects.get(processorname="membership processor")
            invoicerows = [('%s - %s years membership - %s' % (settings.ORG_NAME, cfg.membership_years, request.user.email), 1, cfg.membership_cost, None), ]
            member.activeinvoice = manager.create_invoice(
                request.user,
                request.user.email,
                request.user.first_name + ' ' + request.user.last_name,
                '',  # We don't have an address
                '%s membership for %s' % (settings.ORG_NAME, request.user.email),
                timezone.now(),
                timezone.now(),
                invoicerows,
                processor=processor,
                processorid=member.pk,
                canceltime=timezone.now() + timedelta(days=7),
                accounting_account=settings.ACCOUNTING_MEMBERSHIP_ACCOUNT,
                paymentmethods=cfg.paymentmethods.all(),
                )
            member.activeinvoice.save()
            member.save()

            # We'll redirect back to the same page, so make sure
            # someone doing say a hard refresh on the page doesn't
            # cause weird things to happen.
            return HttpResponseRedirect('/membership/')

        form = MemberForm(data=request.POST, instance=member)
        if form.is_valid():
            member = form.save(commit=False)
            member.user = request.user
            member.save()
            if not registration_complete:
                MemberLog(member=member,
                          timestamp=timezone.now(),
                          message="Registration received, awaiting payment").save()
                registration_complete = True  # So we show the payment info!
            elif form.has_changed():
                # Figure out what changed
                MemberLog(member=member,
                          timestamp=timezone.now(),
                          message="Modified registration data for field(s): %s" % (", ".join(form.changed_data)),
                          ).save()
            return HttpResponseRedirect(".")
    else:
        form = MemberForm(instance=member)

    logdata = MemberLog.objects.filter(member=member).order_by('-timestamp')[:30]

    return render(request, 'membership/index.html', {
        'form': form,
        'member': member,
        'invoice': InvoicePresentationWrapper(member.activeinvoice, "%s/membership/" % settings.SITEBASE),
        'registration_complete': registration_complete,
        'logdata': logdata,
        'amount': cfg.membership_cost,
        'cancelurl': '/account/',
    })
Exemplo n.º 24
0
def home(request):
	try:
		member = Member.objects.get(user=request.user)
		registration_complete = True

		# We have a batch job that expires members, but do it here as well to make sure
		# the web is up to date with information if necessary.
		if member.paiduntil and member.paiduntil < date.today():
			MemberLog(member=member,
					  timestamp=datetime.now(),
					  message="Membership expired").save()
			member.membersince = None
			member.paiduntil = None
			member.save()

	except Member.DoesNotExist:
		# No record yet, so we create one. Base the information on whatever we
		# have already.
		member = Member(user=request.user, fullname=request.user.first_name)
		registration_complete = False

	if request.method == "POST":
		form = MemberForm(data=request.POST, instance=member)
		if form.is_valid():
			member = form.save(commit=False)
			member.user = request.user
			member.save()
			if not registration_complete:
				MemberLog(member=member,
						  timestamp=datetime.now(),
						  message="Registration received, awaiting payment").save()
				registration_complete = True # So we show the payment info!
			elif form.has_changed():
				# Figure out what changed
				MemberLog(member=member,
						  timestamp=datetime.now(),
						  message="Modified registration data for field(s): %s" % (", ".join(form._changed_data)),
						  ).save()
			if request.POST["submit"] == "Generate invoice":
				# Generate an invoice for the user
				if member.activeinvoice:
					raise Exception("This should not happen - generating invoice when one already exists!")
				manager = InvoiceManager()
				processor = InvoiceProcessor.objects.get(processorname="membership processor")
				invoicerows = [('PostgreSQL Europe - 2 years membership - %s' % request.user.email, 1, 10),]
				member.activeinvoice = manager.create_invoice(
					request.user,
					request.user.email,
					request.user.first_name + ' ' + request.user.last_name,
					'', # We don't have an address
					'PostgreSQL Europe membership for %s'% request.user.email,
					datetime.now(),
					datetime.now(),
					invoicerows,
					processor = processor,
					processorid = member.pk,
					bankinfo = False,
					accounting_account = settings.ACCOUNTING_MEMBERSHIP_ACCOUNT
					)
				member.activeinvoice.save()
				member.save()

				# We'll redirect back to the same page, so make sure
				# someone doing say a hard refresh on the page doesn't
				# cause weird things to happen.
				return HttpResponseRedirect('/membership/')
	else:
		form = MemberForm(instance=member)

	logdata = MemberLog.objects.filter(member=member).order_by('-timestamp')[:30]

	return render_to_response('membership/index.html', {
		'form': form,
		'member': member,
		'invoice': InvoicePresentationWrapper(member.activeinvoice, "%s/membership/" % settings.SITEBASE_SSL),
		'registration_complete': registration_complete,
		'logdata': logdata,
		'amount': 10, # price for two years
	}, context_instance=RequestContext(request))
Exemplo n.º 25
0
def process_authorization(notification):
	if notification.success:
		# This is a successful notification, so flag this invoice
		# as paid. We also create a TransactionStatus for it, so that
		# can validate that it goes from authorized->captured.
		trans = TransactionStatus(pspReference=notification.pspReference,
								  notification=notification,
								  authorizedat=datetime.now(),
								  amount=notification.amount,
								  method=notification.paymentMethod,
								  notes=notification.merchantReference,
								  capturedat=None)
		trans.save()

		# Generate urls pointing back to this entry in the Adyen online
		# system, for inclusion in accounting records.
		urls = ["https://ca-live.adyen.com/ca/ca/accounts/showTx.shtml?pspReference=%s&txType=Payment&accountKey=MerchantAccount.%s" % (notification.pspReference, notification.merchantAccountCode),]

		# We can receive authorizations on non-primary Adyen merchant
		# accounts. This happens for example with payments from POS
		# terminals. In those cases, just send an email, and don't
		# try to match it to any invoices.
		# We still store and track the transaction.
		if notification.merchantAccountCode != settings.ADYEN_MERCHANTACCOUNT:
			send_simple_mail(settings.INVOICE_SENDER_EMAIL,
							 settings.ADYEN_NOTIFICATION_RECEIVER,
							 'Manual Adyen payment authorized',
							 "An Adyen payment of %s%s was authorized on the Adyen platform.\nThis payment was not from the automated system, it was manually authorized, probably from a POS terminal.\nReference: %s\nAdyen reference: %s\nMerchant account: %s\n" % (settings.CURRENCY_ABBREV, notification.amount, notification.merchantReference, notification.pspReference, notification.merchantAccountCode))
			notification.confirmed = True
			notification.save()

			# For manual payments, we can only create an open-ended entry
			# in the accounting
			accstr = "Manual Adyen payment: %s (%s)" % (notification.merchantReference, notification.pspReference)
			accrows = [
				(settings.ACCOUNTING_ADYEN_AUTHORIZED_ACCOUNT, accstr, trans.amount, None),
				]
			create_accounting_entry(date.today(), accrows, True, urls)
			return

		# Process a payment on the primary account
		manager = InvoiceManager()
		try:
			# Figure out the invoiceid
			if not notification.merchantReference.startswith(settings.ADYEN_MERCHANTREF_PREFIX):
				raise AdyenProcessingException('Merchant reference does not start with %s' % settings.ADYEN_MERCHANTREF_PREFIX)
			invoiceid = int(notification.merchantReference[len(settings.ADYEN_MERCHANTREF_PREFIX):])

			# Get the actual invoice
			try:
				invoice = Invoice.objects.get(pk=invoiceid)
			except Invoice.DoesNotExist:
				raise AdyenProcessingException('Invoice with id %s does not exist' % invoiceid)

			def invoice_logger(msg):
				raise AdyenProcessingException('Invoice processing failed: %s', msg)

			manager.process_incoming_payment_for_invoice(invoice, notification.amount, 'Adyen id %s' % notification.pspReference, 0, settings.ACCOUNTING_ADYEN_AUTHORIZED_ACCOUNT, 0, urls, invoice_logger)

			if invoice.accounting_object:
				# Store the accounting object so we can properly tag the
				# fee for it when we process the settlement (since we don't
				# actually know the fee yet)
				trans.accounting_object = invoice.accounting_object
				trans.save()

			# If nothing went wrong, then this invoice is now fully
			# flagged as paid in the system.
			send_simple_mail(settings.INVOICE_SENDER_EMAIL,
							 settings.ADYEN_NOTIFICATION_RECEIVER,
							 'Adyen payment authorized',
							 "An Adyen payment of %s%s with reference %s was authorized on the Adyen platform.\nInvoice: %s\nRecipient name: %s\nRecipient user: %s\nAdyen reference: %s\n" % (settings.CURRENCY_ABBREV, notification.amount, notification.merchantReference, invoice.title, invoice.recipient_name, invoice.recipient_email, notification.pspReference))

		except AdyenProcessingException, ex:
			# Generate an email telling us about this exception!
			send_simple_mail(settings.INVOICE_SENDER_EMAIL,
							 settings.ADYEN_NOTIFICATION_RECEIVER,
							 'Exception occured processing Adyen notification',
							 "An exception occured processing the notification for %s:\n\n%s\n" % (
								 notification.merchantReference,
								 ex)
						 )
			# We have stored the notification already, but we want
			# to make sure it's not *confirmed*. That way it'll keep
			# bugging the user. So, return here instead of confirming
			# it.
			return
Exemplo n.º 26
0
def payment_post(request):
    nonce = request.POST['payment_method_nonce']
    invoice = get_object_or_404(Invoice, pk=get_int_or_error(request.POST, 'invoice'), deleted=False, finalized=True)
    method = get_object_or_404(InvoicePaymentMethod, pk=get_int_or_error(request.POST, 'method'), active=True)
    pm = method.get_implementation()

    if invoice.processor:
        manager = InvoiceManager()
        processor = manager.get_invoice_processor(invoice)
        returnurl = processor.get_return_url(invoice)
    else:
        if invoice.recipient_user:
            returnurl = "%s/invoices/%s/" % (settings.SITEBASE, invoice.pk)
        else:
            returnurl = "%s/invoices/%s/%s/" % (settings.SITEBASE, invoice.pk, invoice.recipient_secret)

    # Generate the transaction
    result = pm.braintree_sale({
        'amount': '{0}'.format(invoice.total_amount),
        'order_id': '#{0}'.format(invoice.pk),
        'payment_method_nonce': nonce,
        'merchant_account_id': pm.config('merchantacctid'),
        'options': {
            'submit_for_settlement': True,
        }
    })

    trans = result.transaction
    if result.is_success:
        # Successful transaction. Store it for later processing. At authorization, we proceed to
        # flag the payment as done.

        BraintreeLog(transid=trans.id,
                     message='Received successful result for {0}'.format(trans.id),
                     paymentmethod=method).save()

        if trans.currency_iso_code != settings.CURRENCY_ISO:
            BraintreeLog(transid=trans.id,
                         error=True,
                         message='Invalid currency {0}, should be {1}'.format(trans.currency_iso_code, settings.CURRENCY_ISO),
                         paymentmethod=method).save()

            send_simple_mail(settings.INVOICE_SENDER_EMAIL,
                             pm.config('notification_receiver'),
                             'Invalid currency received in Braintree payment',
                             'Transaction {0} paid in {1}, should be {2}.'.format(trans.id, trans.currency_iso_code, settings.CURRENCY_ISO))

            # We'll just throw the "processing error" page, and have
            # the operator deal with the complaints as this is a
            # should-never-happen scenario.
            return render(request, 'braintreepayment/processing_error.html')

        with transaction.atomic():
            # Flag the invoice as paid
            manager = InvoiceManager()
            try:
                def invoice_logger(msg):
                    raise BraintreeProcessingException('Invoice processing failed: %s'.format(msg))

                manager.process_incoming_payment_for_invoice(invoice,
                                                             trans.amount,
                                                             'Braintree id {0}'.format(trans.id),
                                                             0,
                                                             pm.config('accounting_authorized'),
                                                             0,
                                                             [],
                                                             invoice_logger,
                                                             method,
                                                         )
            except BraintreeProcessingException as ex:
                send_simple_mail(settings.INVOICE_SENDER_EMAIL,
                                 pm.config('notification_receiver'),
                                 'Exception occurred processing Braintree result',
                                 "An exception occured processing the payment result for {0}:\n\n{1}\n".format(trans.id, ex))

                return render(request, 'braintreepayment/processing_error.html')

            # Create a braintree transaction - so we can update it later when the transaction settles
            bt = BraintreeTransaction(transid=trans.id,
                                      authorizedat=timezone.now(),
                                      amount=trans.amount,
                                      method=trans.credit_card['card_type'],
                                      paymentmethod=method)
            if invoice.accounting_object:
                bt.accounting_object = invoice.accounting_object
            bt.save()

            send_simple_mail(settings.INVOICE_SENDER_EMAIL,
                             pm.config('notification_receiver'),
                             'Braintree payment authorized',
                             "A payment of %s%s with reference %s was authorized on the Braintree platform for %s.\nInvoice: %s\nRecipient name: %s\nRecipient user: %s\nBraintree reference: %s\n" % (
                                 settings.CURRENCY_ABBREV,
                                 trans.amount,
                                 trans.id,
                                 method.internaldescription,
                                 invoice.title,
                                 invoice.recipient_name,
                                 invoice.recipient_email,
                                 trans.id))

        return HttpResponseRedirect(returnurl)
    else:
        if not trans:
            reason = "Internal error"
        elif trans.status == 'processor_declined':
            reason = "Processor declined: {0}/{1}".format(trans.processor_response_code, trans.processor_response_text)
        elif trans.status == 'gateway_rejected':
            reason = "Gateway rejected: {0}".format(trans.gateway_rejection_reason)
        else:
            reason = "unknown"
        BraintreeLog(transid=trans and trans.id or "UNKNOWN",
                     message='Received FAILED result for {0}'.format(trans and trans.id or "UNKNOWN"),
                     error=True, paymentmethod=method).save()

        return render(request, 'braintreepayment/payment_failed.html', {
            'invoice': invoice,
            'reason': reason,
            'url': returnurl,
        })
    def process_one_account(self, method):
        pm = method.get_implementation()

        trustly = Trustly(pm)
        manager = InvoiceManager()

        refunds = InvoiceRefund.objects.filter(completed__isnull=True,
                                               invoice__paidusing=method)

        for r in refunds:
            # Find the matching Trustly transaction
            trustlytransactionlist = list(
                TrustlyTransaction.objects.filter(invoiceid=r.invoice.pk,
                                                  paymentmethod=method))
            if len(trustlytransactionlist) == 0:
                raise CommandError(
                    "Could not find trustly transaction for invoice {0}".
                    format(r.invoice.pk))
            elif len(trustlytransactionlist) != 1:
                raise CommandError(
                    "Found {0} trustly transactions for invoice {1}!".format(
                        len(trustlytransactionlist), r.invoice.pk))
            trustlytrans = trustlytransactionlist[0]
            w = trustly.getwithdrawal(trustlytrans.orderid)
            if not w:
                # No refund yet
                continue

            if w['transferstate'] != 'CONFIRMED':
                # Still pending
                continue

            if w['currency'] != settings.CURRENCY_ABBREV:
                # If somebody paid in a different currency (and Trustly converted it for us),
                # the withdrawal entry is specified in the original currency, which is more than
                # a little annoying. To deal with it, attempt to fetch the ledger for the day
                # and if we can find it there, use the amount from that one.
                day = dateutil.parser.parse(w['datestamp']).date()
                ledgerrows = trustly.getledgerforday(day)
                for lr in ledgerrows:
                    if int(lr['orderid']) == trustlytrans.orderid and lr[
                            'accountname'] == 'BANK_WITHDRAWAL_QUEUED':
                        # We found the corresponding accounting row. So we take the amount from
                        # this and convert the difference to what we expeced into the fee. This
                        # can end up being a negative fee, but it should be small enough that
                        # it's not a real problem.
                        fees = (
                            r.fullamount +
                            Decimal(lr['amount']).quantize(Decimal('0.01')))
                        TrustlyLog(
                            message=
                            "Refund for order {0}, invoice {1}, was made as {2} {3} instead of {4} {5}. Using ledger mapped to {6} {7} with difference of {8} {9} booked as fees"
                            .format(
                                trustlytrans.orderid,
                                r.invoice.pk,
                                Decimal(w['amount']),
                                w['currency'],
                                r.fullamount,
                                settings.CURRENCY_ABBREV,
                                Decimal(lr['amount']).quantize(
                                    Decimal('0.01')),
                                settings.CURRENCY_ABBREV,
                                fees,
                                settings.CURRENCY_ABBREV,
                            ),
                            error=False,
                            paymentmethod=method,
                        ).save()
                        break
                else:
                    # Unable to find the refund in the ledger. This could be a matter of timing,
                    # so yell about it but try agian.
                    raise CommandError(
                        "Trustly refund for invoice {0} was made in {1} instead of {2}, but could not be found in ledger."
                        .format(r.invoice.pk, w['currency'],
                                settings.CURRENCY_ABBREV))
            else:
                # Currency is correct, so check that the refunded amount is the same as
                # the one we expected.
                if Decimal(w['amount']) != r.fullamount:
                    raise CommandError(
                        "Mismatch in amount on Trustly refund for invoice {0} ({1} vs {2})"
                        .format(r.invoice.pk, Decimal(w['amount']),
                                r.fullamount))
                fees = 0

            # Ok, things look good!
            TrustlyLog(
                message="Refund for order {0}, invoice {1}, completed".format(
                    trustlytrans.orderid, r.invoice.pk),
                error=False,
                paymentmethod=method).save()
            manager.complete_refund(r.id, r.fullamount, fees,
                                    pm.config('accounting_income'),
                                    pm.config('accounting_fee'), [], method)
Exemplo n.º 28
0
def process_authorization(notification):
    method = notification.rawnotification.paymentmethod
    pm = method.get_implementation()

    if notification.success:
        # This is a successful notification, so flag this invoice
        # as paid. We also create a TransactionStatus for it, so that
        # can validate that it goes from authorized->captured.
        trans = TransactionStatus(pspReference=notification.pspReference,
                                  notification=notification,
                                  authorizedat=datetime.now(),
                                  amount=notification.amount,
                                  method=notification.paymentMethod,
                                  notes=notification.merchantReference,
                                  capturedat=None,
                                  paymentmethod=method)
        trans.save()

        # Generate urls pointing back to this entry in the Adyen online
        # system, for inclusion in accounting records.
        urls = [
            "https://ca-live.adyen.com/ca/ca/accounts/showTx.shtml?pspReference=%s&txType=Payment&accountKey=MerchantAccount.%s"
            % (notification.pspReference, notification.merchantAccountCode),
        ]

        # We can receive authorizations on non-primary Adyen merchant
        # accounts. This happens for example with payments from POS
        # terminals. In those cases, just send an email, and don't
        # try to match it to any invoices.
        # We still store and track the transaction.
        if notification.merchantAccountCode != pm.config('merchantaccount'):
            send_simple_mail(
                settings.INVOICE_SENDER_EMAIL,
                pm.config('notification_receiver'),
                'Manual Adyen payment authorized',
                "An Adyen payment of %s%s was authorized on the Adyen platform for %s.\nThis payment was not from the automated system, it was manually authorized, probably from a POS terminal.\nReference: %s\nAdyen reference: %s\nMerchant account: %s\n"
                %
                (settings.CURRENCY_ABBREV, notification.amount,
                 method.internaldescription, notification.merchantReference,
                 notification.pspReference, notification.merchantAccountCode))
            notification.confirmed = True
            notification.save()

            # For manual payments, we can only create an open-ended entry
            # in the accounting

            accstr = "Manual Adyen payment: %s (%s)" % (
                notification.merchantReference, notification.pspReference)
            accrows = [
                (pm.config('accounting_authorized'), accstr, trans.amount,
                 None),
            ]

            create_accounting_entry(date.today(), accrows, True, urls)
            return

        # Process a payment on the primary account
        manager = InvoiceManager()
        try:
            # Figure out the invoiceid
            if not notification.merchantReference.startswith(
                    pm.config('merchantref_prefix')):
                raise AdyenProcessingException(
                    'Merchant reference does not start with %s' %
                    pm.config('merchantref_prefix'))
            invoiceid = int(
                notification.
                merchantReference[len(pm.config('merchantref_prefix')):])

            # Get the actual invoice
            try:
                invoice = Invoice.objects.get(pk=invoiceid)
            except Invoice.DoesNotExist:
                raise AdyenProcessingException(
                    'Invoice with id %s does not exist' % invoiceid)

            def invoice_logger(msg):
                invoice_logger.invoice_log += msg
                invoice_logger.invoice_log += "\n"

            invoice_logger.invoice_log = ""

            # Handle our special case where an IBAN notification comes in on the creditcard
            # processor (the primary one), but needs to be flagged as the other one.
            # If it can't be found, we just flag it on the other method, since the only
            # thing lost is some statistics.
            if trans.method == 'bankTransfer_IBAN':
                # Find our related method, if it exists
                mlist = list(
                    InvoicePaymentMethod.objects.filter(
                        classname=
                        'postgresqleu.util.payment.adyen.AdyenBanktransfer').
                    extra(
                        where=["config->>'merchantaccount' = %s"],
                        params=[pm.config('merchantaccount')],
                    ))
                if len(mlist) == 1:
                    usedmethod = mlist[0]
                else:
                    usedmethod = method
            else:
                usedmethod = method

            (status, _invoice,
             _processor) = manager.process_incoming_payment_for_invoice(
                 invoice, notification.amount,
                 'Adyen id %s' % notification.pspReference, 0,
                 pm.config('accounting_authorized'), 0, urls, invoice_logger,
                 usedmethod)

            if status != manager.RESULT_OK:
                # An error occurred, but nevertheless the money is in our account at this
                # point. The invoice itself will not have been flagged as paid since something
                # went wrong, and this also means no full accounting record has been created.
                # At this point we have no transaction cost, so we just have the payment itself.
                # Someone will manually have to figure out where to stick it.
                accrows = [
                    (pm.config('accounting_authorized'),
                     "Incorrect payment for invoice #{0}".format(invoice.id),
                     notification.amount, None),
                ]
                create_accounting_entry(date.today(), accrows, True, urls)

                send_simple_mail(
                    settings.INVOICE_SENDER_EMAIL,
                    pm.config('notification_receiver'),
                    'Error processing invoice from Adyen notification',
                    "An error occured processing the notification for invoice #{0} using {1}.\n\nThe messages given were:\n{2}\n\nAn incomplete accounting record has been created, and the situation needs to be handled manually.\n"
                    .format(invoice.id, method.internaldescription,
                            invoice_logger.invoice_log),
                )
                # Actually flag the notification as handled, so we don't end up repeating it.
                notification.confirmed = True
                notification.save()
                return

            if invoice.accounting_object:
                # Store the accounting object so we can properly tag the
                # fee for it when we process the settlement (since we don't
                # actually know the fee yet)
                trans.accounting_object = invoice.accounting_object
                trans.save()

            # If nothing went wrong, then this invoice is now fully
            # flagged as paid in the system.
            send_simple_mail(
                settings.INVOICE_SENDER_EMAIL,
                pm.config('notification_receiver'), 'Adyen payment authorized',
                "An Adyen payment of %s%s with reference %s was authorized on the Adyen platform for %s.\nInvoice: %s\nRecipient name: %s\nRecipient user: %s\nPayment method: %s\nAdyen reference: %s\n"
                % (settings.CURRENCY_ABBREV, notification.amount,
                   notification.merchantReference, method.internaldescription,
                   invoice.title, invoice.recipient_name,
                   invoice.recipient_email, notification.paymentMethod,
                   notification.pspReference))

        except AdyenProcessingException as ex:
            # Generate an email telling us about this exception!
            send_simple_mail(
                settings.INVOICE_SENDER_EMAIL,
                pm.config('notification_receiver'),
                'Exception occured processing Adyen notification',
                "An exception occurred processing the notification for %s on %s:\n\n%s\n"
                % (notification.merchantReference, method.internaldescription,
                   ex))
            # We have stored the notification already, but we want
            # to make sure it's not *confirmed*. That way it'll keep
            # bugging the user. So, return here instead of confirming
            # it.
            return
    else:
        send_simple_mail(
            settings.INVOICE_SENDER_EMAIL, pm.config('notification_receiver'),
            'Unsuccessful Adyen authorization received',
            "A credit card authorization for %s on account %s has failed.\nThe reason given was:\n%s\n\nYou don't need to take any further action, nothing has been confirmed in the systems."
            % (
                notification.merchantReference,
                notification.merchantAccountCode,
                notification.reason,
            ))
    notification.confirmed = True
    notification.save()
Exemplo n.º 29
0
def adyen_return_handler(request, methodid):
    method = get_object_or_404(InvoicePaymentMethod, pk=methodid, active=True)
    pm = method.get_implementation()

    sig = pm.calculate_signature(request.GET)

    if sig != request.GET['merchantSig']:
        return render(request, 'adyen/sigerror.html')

    # We're going to need the invoice for pretty much everything,
    # so attempt to find it.
    if request.GET['merchantReturnData'] != request.GET[
            'merchantReference'] or not request.GET[
                'merchantReturnData'].startswith(
                    pm.config('merchantref_prefix')):
        AdyenLog(pspReference='',
                 message='Return handler received invalid reference %s/%s' %
                 (request.GET['merchantReturnData'],
                  request.GET['merchantReference']),
                 error=True,
                 paymentmethod=method).save()
        return render(
            request, 'adyen/invalidreference.html', {
                'reference':
                "%s//%s" % (request.GET['merchantReturnData'],
                            request.GET['merchantReference']),
            })

    invoiceid = int(request.GET['merchantReturnData']
                    [len(pm.config('merchantref_prefix')):])
    try:
        invoice = Invoice.objects.get(pk=invoiceid)
    except Invoice.DoesNotExist:
        AdyenLog(
            pspReference='',
            message='Return handler could not find invoice for reference %s' %
            request.GET['merchantReturnData'],
            error=True,
            paymentmethod=method).save()
        return render(request, 'adyen/invalidreference.html', {
            'reference': request.GET['merchantReturnData'],
        })
    manager = InvoiceManager()
    if invoice.processor:
        processor = manager.get_invoice_processor(invoice)
        returnurl = processor.get_return_url(invoice)
    else:
        if invoice.recipient_user:
            returnurl = "%s/invoices/%s/" % (settings.SITEBASE, invoice.pk)
        else:
            returnurl = "%s/invoices/%s/%s/" % (settings.SITEBASE, invoice.pk,
                                                invoice.recipient_secret)

    AdyenLog(pspReference='',
             message='Return handler received %s result for %s' %
             (request.GET['authResult'], request.GET['merchantReturnData']),
             error=False,
             paymentmethod=method).save()
    if request.GET['authResult'] == 'REFUSED':
        return render(request, 'adyen/refused.html', {
            'url': returnurl,
        })
    elif request.GET['authResult'] == 'CANCELLED':
        return HttpResponseRedirect(returnurl)
    elif request.GET['authResult'] == 'ERROR':
        return render(request, 'adyen/transerror.html', {
            'url': returnurl,
        })
    elif request.GET['authResult'] == 'PENDING':
        return render(request, 'adyen/pending.html', {
            'url': returnurl,
        })
    elif request.GET['authResult'] == 'AUTHORISED':
        # NOTE! Adyen strongly recommends not reacting on
        # authorized values, but deal with them from the
        # notifications instead. So we'll do that.
        # However, if we reach this point and it's actually
        # already dealt with by the notification arriving
        # asynchronously, redirect the user properly.
        if invoice.paidat:
            # Yup, it's paid, so send the user off to the page
            # that they came from.
            return HttpResponseRedirect(returnurl)

        # Show the user a pending message. The refresh time is dependent
        # on how many times we've seen this one before.
        status, created = ReturnAuthorizationStatus.objects.get_or_create(
            pspReference=request.GET['pspReference'])
        status.seencount += 1
        status.save()
        return render(request, 'adyen/authorized.html', {
            'refresh': 3**status.seencount,
            'url': returnurl,
        })
    else:
        return render(request, 'adyen/invalidresult.html', {
            'result': request.GET['authResult'],
        })
Exemplo n.º 30
0
def webhook(request, methodid):
    sig = request.META['HTTP_STRIPE_SIGNATURE']
    try:
        payload = json.loads(request.body.decode('utf8', errors='ignore'))
    except ValueError:
        return HttpResponse("Invalid JSON", status=400)

    method = InvoicePaymentMethod.objects.get(pk=methodid, classname="postgresqleu.util.payment.stripe.Stripe")
    pm = method.get_implementation()

    sigdata = dict([v.strip().split('=') for v in sig.split(',')])

    sigstr = sigdata['t'] + '.' + request.body.decode('utf8', errors='ignore')
    mac = hmac.new(pm.config('webhook_secret').encode('utf8'),
                   msg=sigstr.encode('utf8'),
                   digestmod=hashlib.sha256)
    if mac.hexdigest() != sigdata['v1']:
        return HttpResponse("Invalid signature", status=400)

    # Signature is OK, figure out what to do
    if payload['type'] == 'checkout.session.completed':
        sessionid = payload['data']['object']['id']
        try:
            co = StripeCheckout.objects.get(sessionid=sessionid)
        except StripeCheckout.DoesNotExist:
            StripeLog(message="Received completed session event for non-existing sessions {}".format(sessionid),
                      error=True,
                      paymentmethod=method).save()
            return HttpResponse("OK")

        # We don't get enough data in the session, unfortunately, so we have to
        # make some incoming API calls.
        StripeLog(message="Received Stripe webhook for checkout {}. Processing.".format(co.id), paymentmethod=method).save()
        process_stripe_checkout(co)
        StripeLog(message="Completed processing webhook for checkout {}.".format(co.id), paymentmethod=method).save()
        return HttpResponse("OK")
    elif payload['type'] == 'charge.refunded':
        chargeid = payload['data']['object']['id']

        # There can be multiple refunds on each charge, so we have to look through all the
        # possible ones, and compare. Unfortunately, there is no notification available which
        # tells us *which one* was completed. Luckily there will never be *many* refunds on a
        # single charge.
        with transaction.atomic():
            for r in payload['data']['object']['refunds']['data']:
                try:
                    refund = StripeRefund.objects.get(paymentmethod=method,
                                                      chargeid=chargeid,
                                                      refundid=r['id'])
                except StripeRefund.DoesNotExist:
                    StripeLog(message="Received completed refund event for non-existant refund {}".format(r['id']),
                              error=True,
                              paymentmethod=method).save()
                    return HttpResponse("OK")
                if refund.completedat:
                    # It wasn't this one, as it's already been completed.
                    continue

                if r['amount'] != refund.amount * 100:
                    StripeLog(message="Received completed refund with amount {0} instead of expected {1} for refund {2}".format(r['amount'] / 100, refund.amount, refund.id),
                              error=True,
                              paymentmethod=method).save()
                    return HttpResponse("OK")

                # OK, refund looks fine
                StripeLog(message="Received Stripe webhook for refund {}. Processing.".format(refund.id), paymentmethod=method).save()

                refund.completedat = datetime.now()
                refund.save()

                manager = InvoiceManager()
                manager.complete_refund(
                    refund.invoicerefundid_id,
                    refund.amount,
                    0,  # Unknown fee
                    pm.config('accounting_income'),
                    pm.config('accounting_fee'),
                    [],
                    method)
        return HttpResponse("OK")
    elif payload['type'] == 'payout.paid':
        # Payout has left Stripe. Should include both automatic and manual ones
        payoutid = payload['data']['object']['id']

        obj = payload['data']['object']
        if obj['currency'].lower() != settings.CURRENCY_ISO.lower():
            StripeLog(message="Received payout in incorrect currency {}, ignoring".format(obj['currency']),
                      error=True,
                      paymentmethod=method).save()
            return HttpResponse("OK")

        with transaction.atomic():
            if StripePayout.objects.filter(payoutid=payoutid).exists():
                StripeLog(message="Received duplicate notification for payout {}, ignoring".format(payoutid),
                          error=True,
                          paymentmethod=method).save()
                return HttpResponse("OK")

            payout = StripePayout(paymentmethod=method,
                                  payoutid=payoutid,
                                  amount=Decimal(obj['amount']) / 100,
                                  sentat=datetime.now(),
                                  description=obj['description'])
            payout.save()

            acctrows = [
                (pm.config('accounting_income'), 'Stripe payout {}'.format(payout.payoutid), -payout.amount, None),
                (pm.config('accounting_payout'), 'Stripe payout {}'.format(payout.payoutid), payout.amount, None),
            ]

            if is_managed_bank_account(pm.config('accounting_payout')):
                entry = create_accounting_entry(date.today(), acctrows, True)

                # XXX: we don't know what this looks like at the other end yet, so put a random string in there
                register_pending_bank_matcher(pm.config('accounting_payout'),
                                              '.*STRIPE_PAYOUT_WHAT_GOES_HERE?.*',
                                              payout.amount,
                                              entry)
                msg = "A Stripe payout with description {} completed for {}.\n\nAccounting entry {} was created and will automatically be closed once the payout has arrived.".format(
                    payout.description,
                    method.internaldescription,
                    entry,
                )
            else:
                msg = "A Stripe payout with description {} completed for {}.\n".format(payout.description, method.internaldescription)

            StripeLog(message=msg, paymentmethod=method).save()
            send_simple_mail(settings.INVOICE_SENDER_EMAIL,
                             pm.config('notification_receiver'),
                             'Stripe payout completed',
                             msg,
            )
            return HttpResponse("OK")
    else:
        StripeLog(message="Received unknown Stripe event type '{}'".format(payload['type']),
                  error=True,
                  paymentmethod=method).save()
        # We still flag it as OK to stripe
        return HttpResponse("OK")
Exemplo n.º 31
0
def create_sponsor_invoice(user, sponsor):
    conference = sponsor.conference
    level = sponsor.level

    if settings.EU_VAT:
        # If a sponsor has an EU VAT Number, we do *not* charge VAT.
        # For any sponsor without a VAT number, charge VAT.
        # Except if the sponsor is from outside the EU, in which case no VAT.
        # If a sponsor is from our home country, meaning they have a
        #  VAT number and it starts with our prefix, charge VAT.
        # XXX: we should probably have *accounting* entries for reverse
        #      VAT on the ones with a number, but EU vat is currently
        #      handled manually outside the process for now.
        if sponsor.vatstatus == 0:
            # Sponsor inside EU with VAT number
            if not sponsor.vatnumber:
                raise Exception("Cannot happen")
            if sponsor.vatnumber.startswith(settings.EU_VAT_HOME_COUNTRY):
                # Home country, so we charge vat
                vatlevel = conference.vat_sponsorship
                reverse_vat = False
            else:
                # Not home country but has VAT number
                vatlevel = None
                reverse_vat = True
        elif sponsor.vatstatus == 1:
            # Sponsor inside EU but no VAT number
            vatlevel = conference.vat_sponsorship
            reverse_vat = False
        else:
            # Sponsor outside EU
            vatlevel = None
            reverse_vat = False
    else:
        # Not caring about EU VAT, so assign whatever the conference said
        vatlevel = conference.vat_sponsorship
        reverse_vat = False

    invoicerows = [
        ['%s %s sponsorship' % (conference, level), 1, level.levelcost, vatlevel],
    ]
    if conference.startdate < date.today() + timedelta(days=5):
        # If conference happens in the next 5 days, invoice is due immediately
        duedate = date.today()
    elif conference.startdate < date.today() + timedelta(days=30):
        # Less than 30 days before the conference, set the due date to
        # 5 days before the conference
        duedate = conference.startdate - timedelta(days=5)
    else:
        # More than 30 days before the conference, set the due date
        # to 30 days from now.
        duedate = datetime.now() + timedelta(days=30)

    manager = InvoiceManager()
    processor = invoicemodels.InvoiceProcessor.objects.get(processorname="confsponsor processor")
    i = manager.create_invoice(
        user,
        user.email,
        user.first_name + ' ' + user.last_name,
        get_sponsor_invoice_address(sponsor.name, sponsor.invoiceaddr, sponsor.vatnumber),
        '%s sponsorship' % conference.conferencename,
        datetime.now(),
        duedate,
        invoicerows,
        processor=processor,
        processorid=sponsor.pk,
        accounting_account=settings.ACCOUNTING_CONFSPONSOR_ACCOUNT,
        accounting_object=conference.accounting_object,
        reverse_vat=reverse_vat,
        extra_bcc_list=conference.sponsoraddr,
        paymentmethods=level.paymentmethods.all(),
    )
    return i