Exemplo n.º 1
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)
Exemplo n.º 2
0
    def handle(self, *args, **options):

        from cms.api import add_plugin
        from cms.models import Page, StaticPlaceholder

        foundErrors = False

        try:
            initial_language = settings.LANGUAGES[0][0]
        except IndexError:
            initial_language = getattr(settings, 'LANGUAGE_CODE', 'en')

        # Do some sanity checks to ensure that necessary apps are listed in INSTALLED_APPS
        # before proceeding
        required_apps = [
            ('cms', 'Django CMS'),
            ('danceschool.core', 'Core danceschool app'),
            ('danceschool.payments.square', 'Square integration app'),
        ]
        for this_app in required_apps:
            if not apps.is_installed(this_app[0]):
                self.stdout.write(
                    self.style.ERROR(
                        'ERROR: %s is not installed or listed in INSTALLED_APPS. Please install before proceeding.'
                        % this_app[1]))
                return None

        self.stdout.write("""
CHECKING SQUARE INTEGRATION
---------------------------
            """)

        location_id = getattr(settings, 'SQUARE_LOCATION_ID', '')
        client_id = getattr(settings, 'SQUARE_APPLICATION_ID', '')
        client_secret = getattr(settings, 'SQUARE_ACCESS_TOKEN', '')

        if location_id:
            self.stdout.write('Square location ID set.')

        else:
            self.stdout.write(self.style.WARNING('Square location ID not set'))
            foundErrors = True

        if client_id:
            self.stdout.write('Square application ID set.')
        else:
            self.stdout.write(
                self.style.WARNING('Square application ID not set'))
            foundErrors = True

        if client_secret:
            self.stdout.write('Square access token set.')
        else:
            self.stdout.write(
                self.style.WARNING('Square access token not set.'))
            foundErrors = True

        if location_id and client_id and client_secret:
            try:
                from squareconnect.rest import ApiException
                from squareconnect.apis.locations_api import LocationsApi
                from squareconnect.apis.transactions_api import TransactionsApi

                locations_api_instance = LocationsApi()
                locations_api_instance.api_client.configuration.access_token = getattr(
                    settings, 'SQUARE_ACCESS_TOKEN', '')
                transactions_api_instance = TransactionsApi()
                transactions_api_instance.api_client.configuration.access_token = getattr(
                    settings, 'SQUARE_ACCESS_TOKEN', '')

                # Check that the location ID from settings actually identifies a location.
                api_response = locations_api_instance.list_locations()
                if api_response.errors:
                    self.stdout.write(
                        self.style.ERROR('Error in listing Locations: %s' %
                                         api_response.errors))
                    foundErrors = True
                if location_id not in [x.id for x in api_response.locations]:
                    self.stdout.write(
                        self.style.ERROR(
                            'Location ID from settings does not identify a valid Square Location.'
                        ))
                    foundErrors = True

                # Check that we can access transaction information
                api_response = transactions_api_instance.list_transactions(
                    location_id=location_id)
                if api_response.errors:
                    self.stdout.write(
                        self.style.ERROR('Error in listing Transactions: %s' %
                                         api_response.errors))
                    foundErrors = True
                else:
                    self.stdout.write(
                        self.style.SUCCESS(
                            'Successfully connected to Square API with provided credentials.'
                        ))
            except ImportError:
                self.stdout.write(
                    self.style.ERROR(
                        'Required squareconnect app not installed.'))
                foundErrors = True
            except ApiException as e:
                self.stdout.write(
                    self.style.ERROR('Exception in using Square API: %s\n' %
                                     e))
                foundErrors = True

        add_square_checkout = self.boolean_input(
            'Add Square Checkout form to the registration summary view to allow students to pay [Y/n]',
            True)
        if add_square_checkout:
            home_page = Page.objects.filter(is_home=True,
                                            publisher_is_draft=False).first()
            if not home_page:
                self.stdout.write(
                    self.style.ERROR(
                        'Cannot add Square Checkout form because a home page has not yet been set.'
                    ))
                foundErrors = True
            else:
                checkout_sp = StaticPlaceholder.objects.get_or_create(
                    code='registration_payment_placeholder')
                checkout_p_draft = checkout_sp[0].draft
                checkout_p_public = checkout_sp[0].public

                if checkout_p_public.get_plugins().filter(
                        plugin_type='SquareCheckoutFormPlugin').exists():
                    self.stdout.write('Square checkout form already present.')
                else:
                    add_plugin(
                        checkout_p_draft,
                        'SquareCheckoutFormPlugin',
                        initial_language,
                        successPage=home_page,
                    )
                    add_plugin(
                        checkout_p_public,
                        'SquareCheckoutFormPlugin',
                        initial_language,
                        successPage=home_page,
                    )
                    self.stdout.write('Square Checkout form added.')
                    self.stdout.write("""

Notes for Checkout integration
------------------------------

- In order for the Square checkout form to function on your
  website, you *must* be able to connect to the site using
  HTTPS, and the page on which your checkout form is included
  must be served over a secure connection via HTTPS.  Be sure
  that your server is set up to permit HTTPS connections, and that
  it automatically directs customers who are registering to
  an HTTPS connection.

- If you are running a development installation of the project
  on your local machine, then the above HTTPS requirement does
  not apply.  You will be able to see and test the checkout form
  on your local machine.

                        """)

        add_square_pos = self.boolean_input(
            'Add Square point-of-sale button to the registration summary view to allow students to pay [Y/n]',
            True)
        if add_square_pos:
            home_page = Page.objects.filter(is_home=True,
                                            publisher_is_draft=False).first()
            if not home_page:
                self.stdout.write(
                    self.style.ERROR(
                        'Cannot add Square point-of-sale button because a home page has not yet been set.'
                    ))
                foundErrors = True
            else:
                checkout_sp = StaticPlaceholder.objects.get_or_create(
                    code='registration_payment_placeholder')
                checkout_p_draft = checkout_sp[0].draft
                checkout_p_public = checkout_sp[0].public

                if checkout_p_public.get_plugins().filter(
                        plugin_type='SquarePointOfSalePlugin').exists():
                    self.stdout.write(
                        'Square point of sale button already present.')
                else:
                    add_plugin(
                        checkout_p_draft,
                        'SquarePointOfSalePlugin',
                        initial_language,
                        successPage=home_page,
                    )
                    add_plugin(
                        checkout_p_public,
                        'SquarePointOfSalePlugin',
                        initial_language,
                        successPage=home_page,
                    )
                    self.stdout.write('Square Checkout form added.')
                    self.stdout.write("""

Notes for point-of-sale integration
-----------------------------------

- Before using the Square point of sale button, you must log in
  and specify the URL on this project to which Square sends
  notifications of each transaction.  This callback URL that you specify
  *must* be a secure HTTPS URL, which means that your server must permit
  HTTPS connections.  To register your callback URL, complete the following
  steps:

    1. Log into the Square website at https://squareup.com/, go to your
    dashboard, and under "Apps > My Apps" select "Manage App" for the
    app whose credentials you have specified for this project.
    2. Under the "Point of Sale API" tab, look for the input box labeled
    "Web Callback URLs."  In that input box, enter the following URL:

    https://%s%s

    3. Click "Save" at the bottom of the page to save your change.

- If you need to test Square point-of-sale integration on a local
  installation, you will need your local installation to be served
  over HTTPS.  Consider a solution such as django-sslserver
  (https://github.com/teddziuba/django-sslserver) for testing purposes.
  Also, be advised that you may need to configure settings on your
  router and/or your computer's firewall in order to ensure that your
  local machine can receive HTTPS callbacks from Square.

- Prior to using the Square point-of-sale button, you must also
  install the Square point-of-sale app on your Android or iOS
  device, and log in using the account whose credentials you have
  specified in the project settings.  If you attempt to begin a point
  of sale transaction without logging into this account, your transaction
  will fail with an error.

                        """ % (Site.objects.get_current().domain,
                               reverse('processSquarePointOfSale')))
        if not foundErrors:
            self.stdout.write(self.style.SUCCESS('Square setup complete.'))
        else:
            self.stdout.write(
                self.style.ERROR(
                    'Square setup encountered errors.  Please see above for details.'
                ))
Exemplo n.º 3
0
    def handle(self, *args, **options):

        from cms.api import add_plugin
        from cms.models import Page, StaticPlaceholder

        foundErrors = False

        try:
            initial_language = settings.LANGUAGES[0][0]
        except IndexError:
            initial_language = getattr(settings, 'LANGUAGE_CODE', 'en')

        # Do some sanity checks to ensure that necessary apps are listed in INSTALLED_APPS
        # before proceeding
        required_apps = [
            ('cms', 'Django CMS'),
            ('danceschool.core', 'Core danceschool app'),
            ('danceschool.payments.square', 'Square integration app'),
        ]
        for this_app in required_apps:
            if not apps.is_installed(this_app[0]):
                self.stdout.write(self.style.ERROR('ERROR: %s is not installed or listed in INSTALLED_APPS. Please install before proceeding.' % this_app[1]))
                return None

        self.stdout.write(
            """
CHECKING SQUARE INTEGRATION
---------------------------
            """
        )

        location_id = getattr(settings,'SQUARE_LOCATION_ID','')
        client_id = getattr(settings,'SQUARE_APPLICATION_ID','')
        client_secret = getattr(settings,'SQUARE_ACCESS_TOKEN','')

        if location_id:
            self.stdout.write('Square location ID set.')

        else:
            self.stdout.write(self.style.WARNING('Square location ID not set'))
            foundErrors = True

        if client_id:
            self.stdout.write('Square application ID set.')
        else:
            self.stdout.write(self.style.WARNING('Square application ID not set'))
            foundErrors = True

        if client_secret:
            self.stdout.write('Square access token set.')
        else:
            self.stdout.write(self.style.WARNING('Square access token not set.'))
            foundErrors = True

        if location_id and client_id and client_secret:
            try:
                import squareconnect
                from squareconnect.rest import ApiException
                from squareconnect.apis.locations_api import LocationsApi
                from squareconnect.apis.transactions_api import TransactionsApi

                squareconnect.configuration.access_token = client_secret
                locations_api_instance = LocationsApi()
                transactions_api_instance = TransactionsApi()

                # Check that the location ID from settings actually identifies a location.
                api_response = locations_api_instance.list_locations()
                if api_response.errors:
                    self.stdout.write(self.style.ERROR('Error in listing Locations: %s' % api_response.errors))
                    foundErrors = True
                if location_id not in [x.id for x in api_response.locations]:
                    self.stdout.write(self.style.ERROR('Location ID from settings does not identify a valid Square Location.'))
                    foundErrors = True

                # Check that we can access transaction information
                api_response = transactions_api_instance.list_transactions(location_id=location_id)
                if api_response.errors:
                    self.stdout.write(self.style.ERROR('Error in listing Transactions: %s' % api_response.errors))
                    foundErrors = True
                else:
                    self.stdout.write(self.style.SUCCESS('Successfully connected to Square API with provided credentials.'))
            except ImportError:
                self.stdout.write(self.style.ERROR('Required squareconnect app not installed.'))
                foundErrors = True
            except ApiException as e:
                self.stdout.write(self.style.ERROR('Exception in using Square API: %s\n' % e))
                foundErrors = True

        add_square_checkout = self.boolean_input('Add Square Checkout form to the registration summary view to allow students to pay [Y/n]', True)
        if add_square_checkout:
            home_page = Page.objects.filter(is_home=True,publisher_public=True).first()
            if not home_page:
                self.stdout.write(self.style.ERROR('Cannot add Square Checkout form because a home page has not yet been set.'))
                foundErrors = True
            else:
                checkout_sp = StaticPlaceholder.objects.get_or_create(code='registration_payment_placeholder')
                checkout_p_draft = checkout_sp[0].draft
                checkout_p_public = checkout_sp[0].public

                if checkout_p_public.get_plugins().filter(plugin_type='SquareCheckoutFormPlugin').exists():
                    self.stdout.write('Square checkout form already present.')
                else:
                    add_plugin(
                        checkout_p_draft, 'SquareCheckoutFormPlugin', initial_language,
                        successPage=home_page,
                    )
                    add_plugin(
                        checkout_p_public, 'SquareCheckoutFormPlugin', initial_language,
                        successPage=home_page,
                    )
                    self.stdout.write('Square Checkout form added.')
                    self.stdout.write(
                        """

Notes for Checkout integration
------------------------------

- In order for the Square checkout form to function on your
  website, you *must* be able to connect to the site using
  HTTPS, and the page on which your checkout form is included
  must be served over a secure connection via HTTPS.  Be sure
  that your server is set up to permit HTTPS connections, and that
  it automatically directs customers who are registering to
  an HTTPS connection.

- If you are running a development installation of the project
  on your local machine, then the above HTTPS requirement does
  not apply.  You will be able to see and test the checkout form
  on your local machine.

                        """
                    )

        add_square_pos = self.boolean_input('Add Square point-of-sale button to the registration summary view to allow students to pay [Y/n]', True)
        if add_square_pos:
            home_page = Page.objects.filter(is_home=True,publisher_public=True).first()
            if not home_page:
                self.stdout.write(self.style.ERROR('Cannot add Square point-of-sale button because a home page has not yet been set.'))
                foundErrors = True
            else:
                checkout_sp = StaticPlaceholder.objects.get_or_create(code='registration_payment_placeholder')
                checkout_p_draft = checkout_sp[0].draft
                checkout_p_public = checkout_sp[0].public

                if checkout_p_public.get_plugins().filter(plugin_type='SquarePointOfSalePlugin').exists():
                    self.stdout.write('Square point of sale button already present.')
                else:
                    add_plugin(
                        checkout_p_draft, 'SquarePointOfSalePlugin', initial_language,
                        successPage=home_page,
                    )
                    add_plugin(
                        checkout_p_public, 'SquarePointOfSalePlugin', initial_language,
                        successPage=home_page,
                    )
                    self.stdout.write('Square Checkout form added.')
                    self.stdout.write(
                        """

Notes for point-of-sale integration
-----------------------------------

- Before using the Square point of sale button, you must log in
  and specify the URL on this project to which Square sends
  notifications of each transaction.  This callback URL that you specify
  *must* be a secure HTTPS URL, which means that your server must permit
  HTTPS connections.  To register your callback URL, complete the following
  steps:

    1. Log into the Square website at https://squareup.com/, go to your
    dashboard, and under "Apps > My Apps" select "Manage App" for the
    app whose credentials you have specified for this project.
    2. Under the "Point of Sale API" tab, look for the input box labeled
    "Web Callback URLs."  In that input box, enter the following URL:

    https://%s%s

    3. Click "Save" at the bottom of the page to save your change.

- If you need to test Square point-of-sale integration on a local
  installation, you will need your local installation to be served
  over HTTPS.  Consider a solution such as django-sslserver
  (https://github.com/teddziuba/django-sslserver) for testing purposes.
  Also, be advised that you may need to configure settings on your
  router and/or your computer's firewall in order to ensure that your
  local machine can receive HTTPS callbacks from Square.

- Prior to using the Square point-of-sale button, you must also
  install the Square point-of-sale app on your Android or iOS
  device, and log in using the account whose credentials you have
  specified in the project settings.  If you attempt to begin a point
  of sale transaction without logging into this account, your transaction
  will fail with an error.

                        """ % (Site.objects.get_current().domain,reverse('processSquarePointOfSale'))
                    )
        if not foundErrors:
            self.stdout.write(self.style.SUCCESS('Square setup complete.'))
        else:
            self.stdout.write(self.style.ERROR('Square setup encountered errors.  Please see above for details.'))