Esempio n. 1
0
def handle_willpayatdoor(request):
    logger.info('Received request for future payment at the door.')
    form = WillPayAtDoorForm(request.POST)

    if form.is_valid():
        tr = form.cleaned_data.get('registration')
        instance = form.cleaned_data.get('instance')

        invoice = Invoice.get_or_create_from_registration(
            tr,
            submissionUser=form.cleaned_data.get('submissionUser'),
        )
        invoice.finalRegistration = tr.finalize()
        invoice.save()
        if instance:
            return HttpResponseRedirect(
                instance.successPage.get_absolute_url())
    return HttpResponseBadRequest()
Esempio n. 2
0
    def ready(self):
        from django.core.exceptions import ValidationError

        from danceschool.core.models import Event, SubstituteTeacher, Invoice, InvoiceItem
        from danceschool.core.constants import getConstant

        # Add some properties to Invoices that are useful for the check-in process.
        # to ensure that the financials are being recorded correctly.

        @property
        def revenueReportedGross(self):
            if hasattr(self, 'revenueitem') and self.revenueitem:
                return self.revenueitem.total
            return 0

        InvoiceItem.add_to_class('revenueReportedGross', revenueReportedGross)

        @property
        def revenueReported(self):
            if hasattr(self, 'revenueitem') and self.revenueitem:
                return self.revenueitem.netRevenue
            return 0

        InvoiceItem.add_to_class('revenueReported', revenueReported)

        @property
        def feesReported(self):
            if hasattr(self, 'revenueitem') and self.revenueitem:
                return self.revenueitem.fees
            return 0

        InvoiceItem.add_to_class('feesReported', feesReported)

        @property
        def taxesReported(self):
            if hasattr(self, 'revenueitem') and self.revenueitem:
                return self.revenueitem.taxes
            return 0

        InvoiceItem.add_to_class('taxesReported', taxesReported)

        @property
        def revenueReceivedGross(self):
            if hasattr(self, 'revenueitem'
                       ) and self.revenueitem and self.revenueitem.received:
                return self.revenueitem.total
            return 0

        InvoiceItem.add_to_class('revenueReceivedGross', revenueReceivedGross)

        @property
        def revenueReceived(self):
            if hasattr(self, 'revenueitem'
                       ) and self.revenueitem and self.revenueitem.received:
                return self.revenueitem.netRevenue
            return 0

        InvoiceItem.add_to_class('revenueReceived', revenueReceived)

        @property
        def revenueMismatch(self):
            comparison = self.revenueReported + \
                self.revenueRefundsReported + self.feesReported
            if not self.invoice.buyerPaysSalesTax:
                comparison += self.taxesReported

            return round(self.total, 2) != round(comparison, 2)

        InvoiceItem.add_to_class('revenueMismatch', revenueMismatch)

        @property
        def invoiceRevenueMismatch(self):
            for x in self.invoiceitem_set.all():
                if x.revenueMismatch:
                    return True

            return False

        Invoice.add_to_class('revenueMismatch', invoiceRevenueMismatch)

        @property
        def revenueNotYetReceived(self):
            return self.revenueReceived != self.revenueReported

        InvoiceItem.add_to_class('revenueNotYetReceived',
                                 revenueNotYetReceived)

        @property
        def invoiceRevenueNotYetReceived(self):
            for x in self.invoiceitem_set.all():
                if x.revenueNotYetReceived:
                    return True

            return False

        Invoice.add_to_class('revenueNotYetReceived',
                             invoiceRevenueNotYetReceived)

        @property
        def revenueRefundsReported(self):
            if hasattr(self, 'revenueitem') and self.revenueitem:
                return -1 * self.revenueitem.adjustments
            return 0

        InvoiceItem.add_to_class('revenueRefundsReported',
                                 revenueRefundsReported)

        @property
        def invoiceRevenueRefundsReported(self):
            return sum(
                [x.revenueRefundsReported for x in self.invoiceitem_set.all()])

        Invoice.add_to_class('revenueRefundsReported',
                             invoiceRevenueRefundsReported)

        # Add a property and a validator to check for and validate that teachers have not
        # already been paid when accepting SubstituteTeacher submissions.
        @property
        def paidOut(self):
            ''' Add a property to Event indicating whether hourly expenses have been paid out. '''
            return (True in self.expenseitem_set.filter(
                expenseRule__staffmemberwageinfo__isnull=False).values_list(
                    'paid', flat=True))

        Event.add_to_class('paidOut', paidOut)

        def validate_EnsureNotPaidOut(event_pk):
            ''' Add a validator to SubstituteTeacher submissions checking if the series has been paid out '''
            event = Event.objects.get(pk=event_pk)
            if event.paidOut:
                raise ValidationError(
                    _('Staff members for this series have already been paid.  If you need to adjust hours worked, you will need to request money from them directly.'
                      ))

        for field in [
                f for f in SubstituteTeacher._meta.fields if f.name == 'event'
        ]:
            field.validators.append(validate_EnsureNotPaidOut)

        # Add get_or_create calls to ensure that the Expense and Revenue categories needed
        # for our handlers exist.  Other categories can always be created, and these can be
        # modified in the database.

        # This ensures that the receivers are loaded.
        from . import handlers
Esempio n. 3
0
    def ready(self):
        from django.core.exceptions import ValidationError

        from danceschool.core.models import Event, SubstituteTeacher, Invoice, InvoiceItem
        from danceschool.core.constants import getConstant

        # Add some properties to Invoices that are useful for the check-in process.
        # to ensure that the financials are being recorded correctly.

        @property
        def revenueReportedGross(self):
            if hasattr(self,'revenueitem') and self.revenueitem:
                return self.revenueitem.total
            return 0
        InvoiceItem.add_to_class('revenueReportedGross',revenueReportedGross)

        @property
        def revenueReported(self):
            if hasattr(self,'revenueitem') and self.revenueitem:
                return self.revenueitem.netRevenue
            return 0
        InvoiceItem.add_to_class('revenueReported',revenueReported)

        @property
        def feesReported(self):
            if hasattr(self,'revenueitem') and self.revenueitem:
                return self.revenueitem.fees
            return 0
        InvoiceItem.add_to_class('feesReported',feesReported)

        @property
        def taxesReported(self):
            if hasattr(self,'revenueitem') and self.revenueitem:
                return self.revenueitem.taxes
            return 0
        InvoiceItem.add_to_class('taxesReported',taxesReported)

        @property
        def revenueReceivedGross(self):
            if hasattr(self,'revenueitem') and self.revenueitem and self.revenueitem.received:
                return self.revenueitem.total
            return 0
        InvoiceItem.add_to_class('revenueReceivedGross',revenueReceivedGross)

        @property
        def revenueReceived(self):
            if hasattr(self,'revenueitem') and self.revenueitem and self.revenueitem.received:
                return self.revenueitem.netRevenue
            return 0
        InvoiceItem.add_to_class('revenueReceived',revenueReceived)

        @property
        def revenueMismatch(self):
            comparison = self.revenueReported + \
                self.revenueRefundsReported + self.feesReported
            if not self.invoice.buyerPaysSalesTax:
                comparison += self.taxesReported

            return round(self.total,2) != round(comparison,2)
        InvoiceItem.add_to_class('revenueMismatch',revenueMismatch)

        @property
        def invoiceRevenueMismatch(self):
            for x in self.invoiceitem_set.all():
                if x.revenueMismatch:
                    return True

            return False
        Invoice.add_to_class('revenueMismatch', invoiceRevenueMismatch)

        @property
        def revenueNotYetReceived(self):
            return self.revenueReceived != self.revenueReported
        InvoiceItem.add_to_class('revenueNotYetReceived',revenueNotYetReceived)

        @property
        def invoiceRevenueNotYetReceived(self):
            for x in self.invoiceitem_set.all():
                if x.revenueNotYetReceived:
                    return True

            return False
        Invoice.add_to_class('revenueNotYetReceived', invoiceRevenueNotYetReceived)

        @property
        def revenueRefundsReported(self):
            if hasattr(self,'revenueitem') and self.revenueitem:
                return -1 * self.revenueitem.adjustments
            return 0
        InvoiceItem.add_to_class('revenueRefundsReported',revenueRefundsReported)

        @property
        def invoiceRevenueRefundsReported(self):
            return sum([x.revenueRefundsReported for x in self.invoiceitem_set.all()])
        Invoice.add_to_class('revenueRefundsReported', invoiceRevenueRefundsReported)

        # Add a property and a validator to check for and validate that teachers have not
        # already been paid when accepting SubstituteTeacher submissions.
        @property
        def paidOut(self):
            ''' Add a property to Event indicating whether hourly expenses have been paid out. '''
            return (True in self.expenseitem_set.filter(expenseRule__staffmemberwageinfo__isnull=False).values_list('paid',flat=True))
        Event.add_to_class('paidOut', paidOut)

        def validate_EnsureNotPaidOut(event_pk):
            ''' Add a validator to SubstituteTeacher submissions checking if the series has been paid out '''
            event = Event.objects.get(pk=event_pk)
            if event.paidOut:
                raise ValidationError(_('Staff members for this series have already been paid.  If you need to adjust hours worked, you will need to request money from them directly.'))

        for field in [f for f in SubstituteTeacher._meta.fields if f.name == 'event']:
            field.validators.append(validate_EnsureNotPaidOut)

        # Add get_or_create calls to ensure that the Expense and Revenue categories needed
        # for our handlers exist.  Other categories can always be created, and these can be
        # modified in the database.

        # This ensures that the receivers are loaded.
        from . import handlers
Esempio n. 4
0
def createPaypalPayment(request):
    '''
    This view handles the creation of Paypal Express Checkout Payment objects.

    All Express Checkout payments must either be associated with a pre-existing Invoice
    or a registration, or they must have an amount and type passed in the post data
    (such as gift certificate payment requests).
    '''
    logger.info('Received request for Paypal Express Checkout payment.')

    invoice_id = request.POST.get('invoice_id')
    tr_id = request.POST.get('reg_id')
    amount = request.POST.get('amount')
    submissionUserId = request.POST.get('user_id')
    transactionType = request.POST.get('transaction_type')
    taxable = request.POST.get('taxable', False)

    # If a specific amount to pay has been passed, then allow payment
    # of that amount.
    if amount:
        try:
            amount = float(amount)
        except ValueError:
            logger.error('Invalid amount passed')
            return HttpResponseBadRequest()

    # Parse if a specific submission user is indicated
    submissionUser = None
    if submissionUserId:
        try:
            submissionUser = User.objects.get(id=int(submissionUserId))
        except (ValueError, ObjectDoesNotExist):
            logger.warning(
                'Invalid user passed, submissionUser will not be recorded.')

    try:
        # Invoice transactions are usually payment on an existing invoice.
        if invoice_id:
            this_invoice = Invoice.objects.get(id=invoice_id)
            this_description = _('Invoice Payment: %s' % this_invoice.id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # This is typical of payment at the time of registration
        elif tr_id:
            tr = TemporaryRegistration.objects.get(id=int(tr_id))
            tr.expirationDate = timezone.now() + timedelta(
                minutes=getConstant('registration__sessionExpiryMinutes'))
            tr.save()
            this_invoice = Invoice.get_or_create_from_registration(
                tr, submissionUser=submissionUser)
            this_description = _('Registration Payment: #%s' % tr_id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # All other transactions require both a transaction type and an amount to be specified
        elif not transactionType or not amount:
            logger.error(
                'Insufficient information passed to createPaypalPayment view.')
            raise ValueError
        else:
            # Gift certificates automatically get a nicer invoice description
            if transactionType == 'Gift Certificate':
                this_description = _('Gift Certificate Purchase')
            else:
                this_description = transactionType
            this_invoice = Invoice.create_from_item(
                float(amount),
                this_description,
                submissionUser=submissionUser,
                calculate_taxes=(taxable is not False),
                transactionType=transactionType,
            )
    except (ValueError, ObjectDoesNotExist) as e:
        logger.error(
            'Invalid registration information passed to createPaypalPayment view: (%s, %s, %s)'
            % (invoice_id, tr_id, amount))
        logger.error(e)
        return HttpResponseBadRequest()

    this_currency = getConstant('general__currencyCode')

    this_total = min(this_invoice.outstandingBalance, amount)
    this_subtotal = this_total - this_invoice.taxes

    this_transaction = {
        'amount': {
            'total': round(this_total, 2),
            'currency': this_currency,
            'details': {
                'subtotal': round(this_subtotal, 2),
                'tax': round(this_invoice.taxes, 2),
            },
        },
        'description': str(this_description),
        'item_list': {
            'items': []
        }
    }

    for item in this_invoice.invoiceitem_set.all():

        if not getConstant('registration__buyerPaysSalesTax'):
            this_item_price = item.grossTotal - item.taxes
        else:
            this_item_price = item.grossTotal

        this_transaction['item_list']['items'].append({
            'name':
            str(item.name),
            'price':
            round(this_item_price, 2),
            'tax':
            round(item.taxes, 2),
            'currency':
            this_currency,
            'quantity':
            1,
        })

    # Because the Paypal API requires that the subtotal add up to the sum of the item
    # totals, we must add a negative line item for discounts applied, and a line item
    # for the remaining balance if there is to be one.
    if this_invoice.grossTotal != this_invoice.total:
        this_transaction['item_list']['items'].append({
            'name':
            str(_('Total Discounts')),
            'price':
            round(this_invoice.total, 2) - round(this_invoice.grossTotal, 2),
            'currency':
            this_currency,
            'quantity':
            1,
        })
    if this_invoice.amountPaid > 0:
        this_transaction['item_list']['items'].append({
            'name':
            str(_('Previously Paid')),
            'price':
            -1 * round(this_invoice.amountPaid, 2),
            'currency':
            this_currency,
            'quantity':
            1,
        })
    if amount != this_invoice.outstandingBalance:
        this_transaction['item_list']['items'].append({
            'name':
            str(_('Remaining Balance After Payment')),
            'price':
            round(amount, 2) - round(this_invoice.outstandingBalance, 2),
            'currency':
            this_currency,
            'quantity':
            1,
        })

    # Paypal requires the Payment request to include redirect URLs.  Since
    # the plugin can handle actual redirects, we just pass the base URL for
    # the current site.
    site = SimpleLazyObject(lambda: get_current_site(request))
    protocol = 'https' if request.is_secure() else 'http'
    base_url = SimpleLazyObject(
        lambda: "{0}://{1}".format(protocol, site.domain))

    payment = Payment({
        'intent': 'sale',
        'payer': {
            'payment_method': 'paypal'
        },
        'transactions': [this_transaction],
        'redirect_urls': {
            'return_url': str(base_url),
            'cancel_url': str(base_url),
        }
    })

    if payment.create():
        logger.info('Paypal payment object created.')

        if this_invoice:
            this_invoice.status = Invoice.PaymentStatus.authorized
            this_invoice.save()

            # We just keep a record of the ID and the status, because the
            # API can be used to look up everything else.
            PaypalPaymentRecord.objects.create(
                paymentId=payment.id,
                invoice=this_invoice,
                status=payment.state,
            )

        return JsonResponse(payment.to_dict())
    else:
        logger.error('Paypal payment object not created.')
        logger.error(payment)
        logger.error(payment.error)
        if this_invoice:
            this_invoice.status = Invoice.PaymentStatus.error
            this_invoice.save()
        return HttpResponseBadRequest()
Esempio n. 5
0
def processSquarePayment(request):
    '''
    This view handles the charging of approved Square Checkout payments.

    All Checkout payments must either be associated with a pre-existing Invoice
    or a registration, or they must have an amount and type passed in the post data
    (such as gift certificate payment requests).
    '''
    logger.info('Received request for Square Checkout payment.')

    nonce_id = request.POST.get('nonce')
    invoice_id = request.POST.get('invoice_id')
    tr_id = request.POST.get('reg_id')
    amount = request.POST.get('amount')
    submissionUserId = request.POST.get('user_id')
    transactionType = request.POST.get('transaction_type')
    taxable = request.POST.get('taxable', False)
    sourceUrl = request.POST.get('sourceUrl', reverse('showRegSummary'))
    addSessionInfo = request.POST.get('addSessionInfo', False)
    successUrl = request.POST.get('successUrl', reverse('registration'))
    customerEmail = request.POST.get('customerEmail')

    # If a specific amount to pay has been passed, then allow payment
    # of that amount.
    if amount:
        try:
            amount = float(amount)
        except ValueError:
            logger.error('Invalid amount passed')
            messages.error(
                request,
                format_html(
                    '<p>{}</p><ul><li>{}</li></ul>',
                    str(
                        _('ERROR: Error with Square checkout transaction attempt.'
                          )), str(_('Invalid amount passed.'))))
            return HttpResponseRedirect(sourceUrl)

    # Parse if a specific submission user is indicated
    submissionUser = None
    if submissionUserId:
        try:
            submissionUser = User.objects.get(id=int(submissionUserId))
        except (ValueError, ObjectDoesNotExist):
            logger.warning(
                'Invalid user passed, submissionUser will not be recorded.')

    try:
        # Invoice transactions are usually payment on an existing invoice.
        if invoice_id:
            this_invoice = Invoice.objects.get(id=invoice_id)
            this_description = _('Invoice Payment: %s' % this_invoice.id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # This is typical of payment at the time of registration
        elif tr_id:
            tr = TemporaryRegistration.objects.get(id=int(tr_id))
            tr.expirationDate = timezone.now() + timedelta(
                minutes=getConstant('registration__sessionExpiryMinutes'))
            tr.save()
            this_invoice = Invoice.get_or_create_from_registration(
                tr, submissionUser=submissionUser)
            this_description = _('Registration Payment: #%s' % tr_id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # All other transactions require both a transaction type and an amount to be specified
        elif not transactionType or not amount:
            logger.error(
                'Insufficient information passed to createSquarePayment view.')
            messages.error(
                request,
                format_html(
                    '<p>{}</p><ul><li>{}</li></ul>',
                    str(
                        _('ERROR: Error with Square checkout transaction attempt.'
                          )),
                    str(
                        _('Insufficient information passed to createSquarePayment view.'
                          ))))
            return HttpResponseRedirect(sourceUrl)
        else:
            # Gift certificates automatically get a nicer invoice description
            if transactionType == 'Gift Certificate':
                this_description = _('Gift Certificate Purchase')
            else:
                this_description = transactionType
            this_invoice = Invoice.create_from_item(
                float(amount),
                this_description,
                submissionUser=submissionUser,
                calculate_taxes=(taxable is not False),
                transactionType=transactionType,
            )
    except (ValueError, ObjectDoesNotExist) as e:
        logger.error(
            'Invalid registration information passed to createSquarePayment view: (%s, %s, %s)'
            % (invoice_id, tr_id, amount))
        messages.error(
            request,
            format_html(
                '<p>{}</p><ul><li>{}</li></ul>',
                str(_(
                    'ERROR: Error with Square checkout transaction attempt.')),
                str(
                    _('Invalid registration information passed to createSquarePayment view: (%s, %s, %s)'
                      % (invoice_id, tr_id, amount)))))
        return HttpResponseRedirect(sourceUrl)

    this_currency = getConstant('general__currencyCode')
    this_total = min(this_invoice.outstandingBalance, amount)

    api_instance = TransactionsApi()
    api_instance.api_client.configuration.access_token = getattr(
        settings, 'SQUARE_ACCESS_TOKEN', '')
    idempotency_key = str(uuid.uuid1())
    location_id = getattr(settings, 'SQUARE_LOCATION_ID', '')
    amount = {'amount': int(100 * this_total), 'currency': this_currency}
    body = {
        'idempotency_key': idempotency_key,
        'card_nonce': nonce_id,
        'amount_money': amount
    }

    errors_list = []

    try:
        # Charge
        api_response = api_instance.charge(location_id, body)
        if api_response.errors:
            logger.error('Error in charging Square transaction: %s' %
                         api_response.errors)
            errors_list = api_response.errors
    except ApiException as e:
        logger.error('Exception when calling TransactionApi->charge: %s\n' % e)
        errors_list = json.loads(e.body).get('errors', [])

    if errors_list:
        this_invoice.status = Invoice.PaymentStatus.error
        this_invoice.save()
        errors_string = ''
        for err in errors_list:
            errors_string += '<li><strong>CODE:</strong> %s, %s</li>' % (
                err.get('code', str(
                    _('Unknown'))), err.get('detail', str(_('Unknown'))))
        messages.error(
            request,
            format_html(
                '<p>{}</p><ul>{}</ul>',
                str(_(
                    'ERROR: Error with Square checkout transaction attempt.')),
                mark_safe(errors_list),
            ))
        return HttpResponseRedirect(sourceUrl)
    else:
        logger.info('Square charge successfully created.')

    transaction = api_response.transaction

    paymentRecord = SquarePaymentRecord.objects.create(
        invoice=this_invoice,
        transactionId=transaction.id,
        locationId=transaction.location_id,
    )

    # We process the payment now, and enqueue the job to retrieve the
    # transaction again once fees have been calculated by Square
    this_invoice.processPayment(
        amount=this_total,
        fees=0,
        paidOnline=True,
        methodName='Square Checkout',
        methodTxn=transaction.id,
        notify=customerEmail,
    )
    updateSquareFees.schedule(args=(paymentRecord, ), delay=60)

    if addSessionInfo:
        paymentSession = request.session.get(INVOICE_VALIDATION_STR, {})

        paymentSession.update({
            'invoiceID': str(this_invoice.id),
            'amount': this_total,
            'successUrl': successUrl,
        })
        request.session[INVOICE_VALIDATION_STR] = paymentSession

    return HttpResponseRedirect(successUrl)
Esempio n. 6
0
def processPointOfSalePayment(request):
    '''
    This view handles the callbacks from point-of-sale transactions.
    Please note that this will only work if you have set up your callback
    URL in Square to point to this view.
    '''
    print('Request data is: %s' % request.GET)

    # iOS transactions put all response information in the data key:
    data = json.loads(request.GET.get('data', '{}'))
    if data:
        status = data.get('status')
        errorCode = data.get('error_code')
        errorDescription = errorCode

        try:
            stateData = data.get('state', '')
            if stateData:
                metadata = json.loads(
                    b64decode(unquote(stateData).encode()).decode())
            else:
                metadata = {}
        except (TypeError, ValueError, binascii.Error):
            logger.error('Invalid metadata passed from Square app.')
            messages.error(
                request,
                format_html(
                    '<p>{}</p><ul><li><strong>CODE:</strong> {}</li><li><strong>DESCRIPTION:</strong> {}</li></ul>',
                    str(
                        _('ERROR: Error with Square point of sale transaction attempt.'
                          )),
                    str(_('Invalid metadata passed from Square app.')),
                ))
            return HttpResponseRedirect(reverse('showRegSummary'))

        # This is the normal transaction identifier, which will be stored in the
        # database as a SquarePaymentRecord
        serverTransId = data.get('transaction_id')

        # This is the only identifier passed for non-card transactions.
        clientTransId = data.get('client_transaction_id')
    else:
        # Android transactions use this GET response syntax
        errorCode = request.GET.get('com.squareup.pos.ERROR_CODE')
        errorDescription = request.GET.get(
            'com.squareup.pos.ERROR_DESCRIPTION')
        status = 'ok' if not errorCode else 'error'

        # This is the normal transaction identifier, which will be stored in the
        # database as a SquarePaymentRecord
        serverTransId = request.GET.get(
            'com.squareup.pos.SERVER_TRANSACTION_ID')

        # This is the only identifier passed for non-card transactions.
        clientTransId = request.GET.get(
            'com.squareup.pos.CLIENT_TRANSACTION_ID')

        # Load the metadata, which includes the registration or invoice ids
        try:
            stateData = request.GET.get('com.squareup.pos.REQUEST_METADATA',
                                        '')
            if stateData:
                metadata = json.loads(
                    b64decode(unquote(stateData).encode()).decode())
            else:
                metadata = {}

        except (TypeError, ValueError, binascii.Error):
            logger.error('Invalid metadata passed from Square app.')
            messages.error(
                request,
                format_html(
                    '<p>{}</p><ul><li><strong>CODE:</strong> {}</li><li><strong>DESCRIPTION:</strong> {}</li></ul>',
                    str(
                        _('ERROR: Error with Square point of sale transaction attempt.'
                          )),
                    str(_('Invalid metadata passed from Square app.')),
                ))
            return HttpResponseRedirect(reverse('showRegSummary'))

    # Other things that can be passed in the metadata
    sourceUrl = metadata.get('sourceUrl', reverse('showRegSummary'))
    successUrl = metadata.get('successUrl', reverse('registration'))
    submissionUserId = metadata.get(
        'userId', getattr(getattr(request, 'user', None), 'id', None))
    transactionType = metadata.get('transaction_type')
    taxable = metadata.get('taxable', False)
    addSessionInfo = metadata.get('addSessionInfo', False)
    customerEmail = metadata.get('customerEmail')

    if errorCode or status != 'ok':
        # Return the user to their original page with the error message displayed.
        logger.error(
            'Error with Square point of sale transaction attempt.  CODE: %s; DESCRIPTION: %s'
            % (errorCode, errorDescription))
        messages.error(
            request,
            format_html(
                '<p>{}</p><ul><li><strong>CODE:</strong> {}</li><li><strong>DESCRIPTION:</strong> {}</li></ul>',
                str(
                    _('ERROR: Error with Square point of sale transaction attempt.'
                      )), errorCode, errorDescription))
        return HttpResponseRedirect(sourceUrl)

    api_instance = TransactionsApi()
    api_instance.api_client.configuration.access_token = getattr(
        settings, 'SQUARE_ACCESS_TOKEN', '')
    location_id = getattr(settings, 'SQUARE_LOCATION_ID', '')

    if serverTransId:
        try:
            api_response = api_instance.retrieve_transaction(
                transaction_id=serverTransId, location_id=location_id)
        except ApiException:
            logger.error('Unable to find Square transaction by server ID.')
            messages.error(
                request,
                _('ERROR: Unable to find Square transaction by server ID.'))
            return HttpResponseRedirect(sourceUrl)
        if api_response.errors:
            logger.error('Unable to find Square transaction by server ID: %s' %
                         api_response.errors)
            messages.error(
                request,
                str(_('ERROR: Unable to find Square transaction by server ID:')
                    ) + api_response.errors)
            return HttpResponseRedirect(sourceUrl)
        transaction = api_response.transaction
    elif clientTransId:
        # Try to find the transaction in the 50 most recent transactions
        try:
            api_response = api_instance.list_transactions(
                location_id=location_id)
        except ApiException:
            logger.error('Unable to find Square transaction by client ID.')
            messages.error(
                request,
                _('ERROR: Unable to find Square transaction by client ID.'))
            return HttpResponseRedirect(sourceUrl)
        if api_response.errors:
            logger.error('Unable to find Square transaction by client ID: %s' %
                         api_response.errors)
            messages.error(
                request,
                str(_('ERROR: Unable to find Square transaction by client ID:')
                    ) + api_response.errors)
            return HttpResponseRedirect(sourceUrl)
        transactions_list = [
            x for x in api_response.transactions
            if x.client_id == clientTransId
        ]
        if len(transactions_list) == 1:
            transaction = transactions_list[0]
        else:
            logger.error('Returned client transaction ID not found.')
            messages.error(
                request, _('ERROR: Returned client transaction ID not found.'))
            return HttpResponseRedirect(sourceUrl)
    else:
        logger.error(
            'An unknown error has occurred with Square point of sale transaction attempt.'
        )
        messages.error(
            request,
            _('ERROR: An unknown error has occurred with Square point of sale transaction attempt.'
              ))
        return HttpResponseRedirect(sourceUrl)

    # Get total information from the transaction for handling invoice.
    this_total = sum([x.amount_money.amount / 100 for x in transaction.tenders or []]) - \
        sum([x.amount_money.amount / 100 for x in transaction.refunds or []])

    # Parse if a specific submission user is indicated
    submissionUser = None
    if submissionUserId:
        try:
            submissionUser = User.objects.get(id=int(submissionUserId))
        except (ValueError, ObjectDoesNotExist):
            logger.warning(
                'Invalid user passed, submissionUser will not be recorded.')

    if 'registration' in metadata.keys():
        try:
            tr_id = int(metadata.get('registration'))
            tr = TemporaryRegistration.objects.get(id=tr_id)
        except (ValueError, TypeError, ObjectDoesNotExist):
            logger.error('Invalid registration ID passed: %s' %
                         metadata.get('registration'))
            messages.error(
                request,
                str(_('ERROR: Invalid registration ID passed')) +
                ': %s' % metadata.get('registration'))
            return HttpResponseRedirect(sourceUrl)

        tr.expirationDate = timezone.now() + timedelta(
            minutes=getConstant('registration__sessionExpiryMinutes'))
        tr.save()
        this_invoice = Invoice.get_or_create_from_registration(
            tr, submissionUser=submissionUser)
        this_description = _('Registration Payment: #%s' % tr_id)

    elif 'invoice' in metadata.keys():
        try:
            this_invoice = Invoice.objects.get(id=int(metadata.get('invoice')))
            this_description = _('Invoice Payment: %s' % this_invoice.id)

        except (ValueError, TypeError, ObjectDoesNotExist):
            logger.error('Invalid invoice ID passed: %s' %
                         metadata.get('invoice'))
            messages.error(
                request,
                str(_('ERROR: Invalid invoice ID passed')) +
                ': %s' % metadata.get('invoice'))
            return HttpResponseRedirect(sourceUrl)
    else:
        # Gift certificates automatically get a nicer invoice description
        if transactionType == 'Gift Certificate':
            this_description = _('Gift Certificate Purchase')
        else:
            this_description = transactionType
        this_invoice = Invoice.create_from_item(
            this_total,
            this_description,
            submissionUser=submissionUser,
            calculate_taxes=(taxable is not False),
            transactionType=transactionType,
        )

    paymentRecord, created = SquarePaymentRecord.objects.get_or_create(
        transactionId=transaction.id,
        locationId=transaction.location_id,
        defaults={
            'invoice': this_invoice,
        })
    if created:
        # We process the payment now, and enqueue the job to retrieve the
        # transaction again once fees have been calculated by Square
        this_invoice.processPayment(
            amount=this_total,
            fees=0,
            paidOnline=True,
            methodName='Square Point of Sale',
            methodTxn=transaction.id,
            notify=customerEmail,
        )
    updateSquareFees.schedule(args=(paymentRecord, ), delay=60)

    if addSessionInfo:
        paymentSession = request.session.get(INVOICE_VALIDATION_STR, {})

        paymentSession.update({
            'invoiceID': str(this_invoice.id),
            'amount': this_total,
            'successUrl': successUrl,
        })
        request.session[INVOICE_VALIDATION_STR] = paymentSession

    return HttpResponseRedirect(successUrl)
Esempio n. 7
0
def handle_stripe_checkout(request):

    logger.info('Received request for Stripe Checkout payment.')

    stripeToken = request.POST.get('stripeToken')
    stripeEmail = request.POST.get('stripeEmail')
    submissionUserId = request.POST.get('submissionUser')
    amount = request.POST.get('stripeAmount')
    invoice_id = request.POST.get('invoice_id')
    tr_id = request.POST.get('reg_id')
    transactionType = request.POST.get('transaction_type')
    taxable = request.POST.get('taxable', False)
    addSessionInfo = request.POST.get('addSessionInfo', False)
    customizeUrl = request.POST.get('customizeUrl')
    successUrl = request.POST.get('successUrl', reverse('registration'))

    # Parse if a specific submission user is indicated
    submissionUser = None
    if submissionUserId:
        try:
            submissionUser = User.objects.get(id=int(submissionUserId))
        except (ValueError, ObjectDoesNotExist):
            logger.warning(
                'Invalid user passed, submissionUser will not be recorded.')

    # If a specific amount to pay has been passed, then allow payment
    # of that amount.
    if amount:
        try:
            if isinstance(amount, list):
                amount = float(amount[0])
            else:
                amount = float(amount)
        except ValueError:
            logger.error('Invalid amount passed')
            return HttpResponseBadRequest()

    # If the details of this transaction are to be entered into session info, then
    # the view must redirect to an interim URL (e.g. for gift certificates) that
    # handles that data
    if addSessionInfo and not customizeUrl:
        logger.error(
            'Request to pass session info without specifying interim URL.')
        return HttpResponseBadRequest()

    try:
        # Invoice transactions are usually payment on an existing invoice.
        if invoice_id:
            this_invoice = Invoice.objects.get(id=invoice_id)
            this_description = _('Invoice Payment: %s' % this_invoice.id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # This is typical of payment at the time of registration
        elif tr_id:
            tr = TemporaryRegistration.objects.get(id=int(tr_id))
            this_invoice = Invoice.get_or_create_from_registration(
                tr, submissionUser=submissionUser)
            this_description = _('Registration Payment: #%s' % tr_id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # All other transactions require both a transaction type and an amount to be specified
        elif not transactionType or not amount:
            logger.error(
                'Insufficient information passed to createPaypalPayment view.')
            raise ValueError
        else:
            # Gift certificates automatically get a nicer invoice description
            if transactionType == 'Gift Certificate':
                this_description = _('Gift Certificate Purchase')
            else:
                this_description = transactionType
            this_invoice = Invoice.create_from_item(
                float(amount),
                this_description,
                submissionUser=submissionUser,
                calculate_taxes=(taxable is not False),
                transactionType=transactionType,
            )
    except (ValueError, ObjectDoesNotExist) as e:
        logger.error(
            'Invalid registration information passed to handle_stripe_checkout view: (%s, %s, %s)'
            % (invoice_id, tr_id, amount))
        logger.error(e)
        return HttpResponseBadRequest()

    this_total = int(min(this_invoice.outstandingBalance, amount) * 100)
    charge = None

    try:
        # Use Stripe's library to make requests...
        charge = stripe.Charge.create(
            amount=this_total,
            currency=getConstant('general__currencyCode'),
            description=this_description,
            source=stripeToken,
        )

    except stripe.error.CardError as e:
        # Since it's a decline, stripe.error.CardError will be caught
        body = e.json_body
        err = body['error']
        logger.error('Stripe CardError %s: %s' % (e.http_status, err))
    except stripe.error.RateLimitError as e:
        # Too many requests made to the API too quickly
        body = e.json_body
        err = body['error']
        logger.error('Stripe RateLimitError %s: %s' % (e.http_status, err))
    except stripe.error.InvalidRequestError as e:
        # Invalid parameters were supplied to Stripe's API
        body = e.json_body
        err = body['error']
        logger.error('Stripe InvalidRequestError %s: %s' %
                     (e.http_status, err))
    except stripe.error.AuthenticationError as e:
        # Authentication with Stripe's API failed
        # (maybe you changed API keys recently)
        body = e.json_body
        err = body['error']
        logger.error('Stripe AuthenticationError %s: %s' %
                     (e.http_status, err))
    except stripe.error.APIConnectionError as e:
        # Network communication with Stripe failed
        body = e.json_body
        err = body['error']
        logger.error('Stripe APIConnectionError %s: %s' % (e.http_status, err))
    except stripe.error.StripeError as e:
        # Display a very generic error to the user, and maybe send
        # yourself an email
        body = e.json_body
        err = body['error']
        logger.error('Stripe StripeError %s: %s' % (e.http_status, err))

    if charge:
        StripeCharge.objects.create(
            chargeId=charge.id,
            status=charge.status,
            submissionUser=submissionUser,
            invoice=this_invoice,
        )

        # To determine the fees applied, we also need to get the balanceTransaction
        # that reports them.
        balanceTransaction = stripe.BalanceTransaction.retrieve(
            charge.balance_transaction)

        this_invoice.processPayment(
            amount=charge.amount / 100,
            fees=balanceTransaction.fee / 100,
            paidOnline=True,
            methodName='Stripe Checkout',
            methodTxn=charge.id,
            submissionUser=submissionUser,
            notify=stripeEmail,
        )

        if addSessionInfo:
            paymentSession = request.session.get(INVOICE_VALIDATION_STR, {})

            paymentSession.update({
                'invoiceID': str(this_invoice.id),
                'amount': charge.amount / 100,
                'successUrl': successUrl,
            })
            request.session[INVOICE_VALIDATION_STR] = paymentSession
            return HttpResponseRedirect(customizeUrl)

        return HttpResponseRedirect(successUrl)

    else:
        # TODO: Improve error handling.
        return JsonResponse(err)
Esempio n. 8
0
def handle_stripe_checkout(request):

    logger.info('Received request for Stripe Checkout payment.')

    stripeToken = request.POST.get('stripeToken')
    stripeEmail = request.POST.get('stripeEmail')
    submissionUserId = request.POST.get('submissionUser')
    amount = request.POST.get('stripeAmount')
    invoice_id = request.POST.get('invoice_id')
    tr_id = request.POST.get('reg_id')
    transactionType = request.POST.get('transaction_type')
    taxable = request.POST.get('taxable', False)
    addSessionInfo = request.POST.get('addSessionInfo',False)
    customizeUrl = request.POST.get('customizeUrl')
    successUrl = request.POST.get('successUrl',reverse('registration'))

    # Parse if a specific submission user is indicated
    submissionUser = None
    if submissionUserId:
        try:
            submissionUser = User.objects.get(id=int(submissionUserId))
        except (ValueError,ObjectDoesNotExist):
            logger.warning('Invalid user passed, submissionUser will not be recorded.')

    # If a specific amount to pay has been passed, then allow payment
    # of that amount.
    if amount:
        try:
            if isinstance(amount,list):
                amount = float(amount[0])
            else:
                amount = float(amount)
        except ValueError:
            logger.error('Invalid amount passed')
            return HttpResponseBadRequest()

    # If the details of this transaction are to be entered into session info, then
    # the view must redirect to an interim URL (e.g. for gift certificates) that
    # handles that data
    if addSessionInfo and not customizeUrl:
        logger.error('Request to pass session info without specifying interim URL.')
        return HttpResponseBadRequest()

    try:
        # Invoice transactions are usually payment on an existing invoice.
        if invoice_id:
            this_invoice = Invoice.objects.get(id=invoice_id)
            this_description = _('Invoice Payment: %s' % this_invoice.id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # This is typical of payment at the time of registration
        elif tr_id:
            tr = TemporaryRegistration.objects.get(id=int(tr_id))
            tr.expirationDate = timezone.now() + timedelta(minutes=getConstant('registration__sessionExpiryMinutes'))
            tr.save()
            this_invoice = Invoice.get_or_create_from_registration(tr, submissionUser=submissionUser)
            this_description = _('Registration Payment: #%s' % tr_id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # All other transactions require both a transaction type and an amount to be specified
        elif not transactionType or not amount:
            logger.error('Insufficient information passed to createPaypalPayment view.')
            raise ValueError
        else:
            # Gift certificates automatically get a nicer invoice description
            if transactionType == 'Gift Certificate':
                this_description = _('Gift Certificate Purchase')
            else:
                this_description = transactionType
            this_invoice = Invoice.create_from_item(
                float(amount),
                this_description,
                submissionUser=submissionUser,
                calculate_taxes=(taxable is not False),
                transactionType=transactionType,
            )
    except (ValueError, ObjectDoesNotExist) as e:
        logger.error('Invalid registration information passed to handle_stripe_checkout view: (%s, %s, %s)' % (invoice_id, tr_id, amount))
        logger.error(e)
        return HttpResponseBadRequest()

    this_total = int(min(this_invoice.outstandingBalance, amount) * 100)
    charge = None

    try:
        # Use Stripe's library to make requests...
        charge = stripe.Charge.create(
            amount=this_total,
            currency=getConstant('general__currencyCode'),
            description=this_description,
            source=stripeToken,
        )

    except stripe.error.CardError as e:
        # Since it's a decline, stripe.error.CardError will be caught
        body = e.json_body
        err = body['error']
        logger.error('Stripe CardError %s: %s' % (e.http_status,err))
    except stripe.error.RateLimitError as e:
        # Too many requests made to the API too quickly
        body = e.json_body
        err = body['error']
        logger.error('Stripe RateLimitError %s: %s' % (e.http_status,err))
    except stripe.error.InvalidRequestError as e:
        # Invalid parameters were supplied to Stripe's API
        body = e.json_body
        err = body['error']
        logger.error('Stripe InvalidRequestError %s: %s' % (e.http_status,err))
    except stripe.error.AuthenticationError as e:
        # Authentication with Stripe's API failed
        # (maybe you changed API keys recently)
        body = e.json_body
        err = body['error']
        logger.error('Stripe AuthenticationError %s: %s' % (e.http_status,err))
    except stripe.error.APIConnectionError as e:
        # Network communication with Stripe failed
        body = e.json_body
        err = body['error']
        logger.error('Stripe APIConnectionError %s: %s' % (e.http_status,err))
    except stripe.error.StripeError as e:
        # Display a very generic error to the user, and maybe send
        # yourself an email
        body = e.json_body
        err = body['error']
        logger.error('Stripe StripeError %s: %s' % (e.http_status,err))

    if charge:
        StripeCharge.objects.create(
            chargeId=charge.id,
            status=charge.status,
            submissionUser=submissionUser,
            invoice=this_invoice,
        )

        # To determine the fees applied, we also need to get the balanceTransaction
        # that reports them.
        balanceTransaction = stripe.BalanceTransaction.retrieve(charge.balance_transaction)

        this_invoice.processPayment(
            amount=charge.amount / 100,
            fees=balanceTransaction.fee / 100,
            paidOnline=True,
            methodName='Stripe Checkout',
            methodTxn=charge.id,
            submissionUser=submissionUser,
            notify=stripeEmail,
        )

        if addSessionInfo:
            paymentSession = request.session.get(INVOICE_VALIDATION_STR, {})

            paymentSession.update({
                'invoiceID': str(this_invoice.id),
                'amount': charge.amount / 100,
                'successUrl': successUrl,
            })
            request.session[INVOICE_VALIDATION_STR] = paymentSession
            return HttpResponseRedirect(customizeUrl)

        return HttpResponseRedirect(successUrl)

    else:
        # TODO: Improve error handling.
        return JsonResponse(err)
Esempio n. 9
0
def createPaypalPayment(request):
    '''
    This view handles the creation of Paypal Express Checkout Payment objects.

    All Express Checkout payments must either be associated with a pre-existing Invoice
    or a registration, or they must have an amount and type passed in the post data
    (such as gift certificate payment requests).
    '''
    logger.info('Received request for Paypal Express Checkout payment.')

    invoice_id = request.POST.get('invoice_id')
    tr_id = request.POST.get('reg_id')
    amount = request.POST.get('amount')
    submissionUserId = request.POST.get('user_id')
    transactionType = request.POST.get('transaction_type')
    taxable = request.POST.get('taxable', False)

    # If a specific amount to pay has been passed, then allow payment
    # of that amount.
    if amount:
        try:
            amount = float(amount)
        except ValueError:
            logger.error('Invalid amount passed')
            return HttpResponseBadRequest()

    # Parse if a specific submission user is indicated
    submissionUser = None
    if submissionUserId:
        try:
            submissionUser = User.objects.get(id=int(submissionUserId))
        except (ValueError, ObjectDoesNotExist):
            logger.warning('Invalid user passed, submissionUser will not be recorded.')

    try:
        # Invoice transactions are usually payment on an existing invoice.
        if invoice_id:
            this_invoice = Invoice.objects.get(id=invoice_id)
            this_description = _('Invoice Payment: %s' % this_invoice.id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # This is typical of payment at the time of registration
        elif tr_id:
            tr = TemporaryRegistration.objects.get(id=int(tr_id))
            tr.expirationDate = timezone.now() + timedelta(minutes=getConstant('registration__sessionExpiryMinutes'))
            tr.save()
            this_invoice = Invoice.get_or_create_from_registration(tr, submissionUser=submissionUser)
            this_description = _('Registration Payment: #%s' % tr_id)
            if not amount:
                amount = this_invoice.outstandingBalance
        # All other transactions require both a transaction type and an amount to be specified
        elif not transactionType or not amount:
            logger.error('Insufficient information passed to createPaypalPayment view.')
            raise ValueError
        else:
            # Gift certificates automatically get a nicer invoice description
            if transactionType == 'Gift Certificate':
                this_description = _('Gift Certificate Purchase')
            else:
                this_description = transactionType
            this_invoice = Invoice.create_from_item(
                float(amount),
                this_description,
                submissionUser=submissionUser,
                calculate_taxes=(taxable is not False),
                transactionType=transactionType,
            )
    except (ValueError, ObjectDoesNotExist) as e:
        logger.error('Invalid registration information passed to createPaypalPayment view: (%s, %s, %s)' % (invoice_id, tr_id, amount))
        logger.error(e)
        return HttpResponseBadRequest()

    this_currency = getConstant('general__currencyCode')

    this_total = min(this_invoice.outstandingBalance, amount)
    this_subtotal = this_total - this_invoice.taxes

    this_transaction = {
        'amount': {
            'total': round(this_total,2),
            'currency': this_currency,
            'details': {
                'subtotal': round(this_subtotal,2),
                'tax': round(this_invoice.taxes,2),
            },
        },
        'description': str(this_description),
        'item_list': {
            'items': []
        }
    }

    for item in this_invoice.invoiceitem_set.all():

        if not getConstant('registration__buyerPaysSalesTax'):
            this_item_price = item.grossTotal - item.taxes
        else:
            this_item_price = item.grossTotal

        this_transaction['item_list']['items'].append({
            'name': str(item.name),
            'price': round(this_item_price,2),
            'tax': round(item.taxes,2),
            'currency': this_currency,
            'quantity': 1,
        })

    # Because the Paypal API requires that the subtotal add up to the sum of the item
    # totals, we must add a negative line item for discounts applied, and a line item
    # for the remaining balance if there is to be one.
    if this_invoice.grossTotal != this_invoice.total:
        this_transaction['item_list']['items'].append({
            'name': str(_('Total Discounts')),
            'price': round(this_invoice.total,2) - round(this_invoice.grossTotal,2),
            'currency': this_currency,
            'quantity': 1,
        })
    if this_invoice.amountPaid > 0:
        this_transaction['item_list']['items'].append({
            'name': str(_('Previously Paid')),
            'price': -1 * round(this_invoice.amountPaid,2),
            'currency': this_currency,
            'quantity': 1,
        })
    if amount != this_invoice.outstandingBalance:
        this_transaction['item_list']['items'].append({
            'name': str(_('Remaining Balance After Payment')),
            'price': round(amount,2) - round(this_invoice.outstandingBalance,2),
            'currency': this_currency,
            'quantity': 1,
        })

    # Paypal requires the Payment request to include redirect URLs.  Since
    # the plugin can handle actual redirects, we just pass the base URL for
    # the current site.
    site = SimpleLazyObject(lambda: get_current_site(request))
    protocol = 'https' if request.is_secure() else 'http'
    base_url = SimpleLazyObject(lambda: "{0}://{1}".format(protocol, site.domain))

    payment = Payment({
        'intent': 'sale',
        'payer': {
            'payment_method': 'paypal'
        },
        'transactions': [this_transaction],
        'redirect_urls': {
            'return_url': str(base_url),
            'cancel_url': str(base_url),
        }
    })

    if payment.create():
        logger.info('Paypal payment object created.')

        if this_invoice:
            this_invoice.status = Invoice.PaymentStatus.authorized
            this_invoice.save()

            # We just keep a record of the ID and the status, because the
            # API can be used to look up everything else.
            PaypalPaymentRecord.objects.create(
                paymentId=payment.id,
                invoice=this_invoice,
                status=payment.state,
            )

        return JsonResponse(payment.to_dict())
    else:
        logger.error('Paypal payment object not created.')
        logger.error(payment)
        logger.error(payment.error)
        if this_invoice:
            this_invoice.status = Invoice.PaymentStatus.error
            this_invoice.save()
        return HttpResponseBadRequest()