def getPayment(self): api_instance = TransactionsApi() try: response = api_instance.retrieve_transaction( location_id=self.locationId, transaction_id=self.transactionId) if response.errors: logger.error( 'Unable to retrieve Square transaction from record.') return None except ApiException as e: logger.error('Unable to retrieve Square transaction from record.') return None return response.transaction
def getPayment(self): api_instance = TransactionsApi() api_instance.api_client.configuration.access_token = getattr( settings, 'SQUARE_ACCESS_TOKEN', '') try: response = api_instance.retrieve_transaction( location_id=self.locationId, transaction_id=self.transactionId) if response.errors: logger.error( 'Unable to retrieve Square transaction from record.') return None except ApiException as e: logger.error('Unable to retrieve Square transaction from record.') return None return response.transaction
def chargePayment(orderId, ccData, ipAddress): try: order = Order.objects.get(id=orderId) idempotency_key = str(uuid.uuid1()) convertedTotal = int(order.total*100) amount = {'amount': convertedTotal, 'currency': settings.SQUARE_CURRENCY} billing_address = {'address_line_1': ccData["address1"], 'address_line_2': ccData["address2"], 'locality': ccData["city"], 'administrative_district_level_1': ccData["state"], 'postal_code': ccData["postal"], 'country': ccData["country"], 'buyer_email_address': ccData["email"], 'first_name': ccData["cc_firstname"], 'last_name': ccData["cc_lastname"]} body = {'idempotency_key': idempotency_key, 'card_nonce': ccData["nonce"], 'amount_money': amount, 'reference_id': order.reference, 'billing_address': billing_address} print("---- Begin Transaction ----") print(body) api_instance = TransactionsApi() api_instance.api_client.configuration.access_token = settings.SQUARE_ACCESS_TOKEN; api_response = api_instance.charge(settings.SQUARE_LOCATION_ID, body) print("---- Charge Submitted ----") print(api_response) if api_response.errors and len(api_response.errors) > 0: message = api_response.errors[0].details print("---- Transaction Failed ----") return False, message print("---- End Transaction ----") return True, "" except ApiException as e: print("---- Transaction Failed ----") print e print("---- End Transaction ----") logger.exception("!!Failed Square Transaction!!") return False, "An unexpected error has occurred."
def process_square(price): if not request.form or 'nonce' not in request.form: return "Bad Request", 422 square = Square.query.first() nonce = request.form['nonce'] api_client = ApiClient() api_client.configuration.access_token = square.access_token customers_api = CustomersApi(api_client) customer_request = CreateCustomerRequest(email_address=current_user.email) try: customer_res = customers_api.create_customer(customer_request) except Exception as e: flash('Card could not be processed.') current_app.logger.error(e, exc_info=True) return redirect(url_for('main.support')) customer = customer_res.customer if customer is None: flash('Card could not be processed.') current_app.logger.info(f''' {current_user.username} card declined: {customer_res.errors} ''') return redirect(url_for('main.support')) else: customer_card_request = CreateCustomerCardRequest(card_nonce=nonce, ) try: card_res = customers_api.create_customer_card( customer.id, customer_card_request, ) except Exception as e: flash('Card could not be processed.') current_app.logger.error(e, exc_info=True) return redirect(url_for('main.support')) card = card_res.card if card is None: flash('Card could not be processed.') current_app.logger.info(f''' {current_user.username} card declined: {card_res.errors} ''') return redirect(url_for('main.support')) else: current_user.square_id = customer.id current_user.square_card = card.id transactions_api = TransactionsApi(api_client) idempotency_key = str(uuid.uuid1()) cents = price * 100 amount = {'amount': cents, 'currency': 'USD'} body = { 'idempotency_key': idempotency_key, 'customer_id': current_user.square_id, 'customer_card_id': current_user.square_card, 'amount_money': amount, } try: charge_response = transactions_api.charge(square.location_id, body) except Exception as e: flash('Card could not be processed.') current_app.logger.error(e, exc_info=True) return redirect(url_for('main.support')) transaction = charge_response.transaction if transaction is None: flash('Card could not be processed.') current_app.logger.info(f''' {current_user.username} card declined: {charge_response.errors} ''') return redirect(url_for('main.support')) elif transaction.id is not None: flash('Subscription Updated') if current_user.expiration <= datetime.today(): base = datetime.today() else: base = current_user.expiration current_user.expiration = base + timedelta(days=30) new_role = PriceLevel.query.filter_by(price=price).first() if hasattr(new_role, 'name'): current_user.role = new_role.name else: current_user.role = PriceLevel.query.first().name current_app.logger.error(f'{current_user.username} \ signed up for nonexistent price level.') db.session.commit() return redirect(url_for('main.index'))
from squareconnect.rest import ApiException from squareconnect.apis.locations_api import LocationsApi api_instance = LocationsApi() api_instance.api_client.configuration.access_token = 'EAAAEOBMM7-B8GAqhBJEIqAgodrXyq7EmVZc_uGdx_GxdqF-IQG6lwITGsmPdV9J' api_response = api_instance.list_locations() print (api_response.locations) from squareconnect.apis.transactions_api import TransactionsApi api_instance = TransactionsApi() api_instance.api_client.configuration.access_token = 'EAAAEOBMM7-B8GAqhBJEIqAgodrXyq7EmVZc_uGdx_GxdqF-IQG6lwITGsmPdV9J' api_response = api_instance.list_transactions(location_id='3KK1N9SVK8QP1') from squareconnect.apis.orders_api import OrdersApi api_instance = OrdersApi() api_instance.api_client.configuration.access_token = 'EAAAEOBMM7-B8GAqhBJEIqAgodrXyq7EmVZc_uGdx_GxdqF-IQG6lwITGsmPdV9J' body = { "idempotency_key": "8193148c-9586-11e6-99f9-28cfe92138cf", "line_items": [ { "name": "coke", "quantity": "2", "variation_name" : "Regular", "base_price_money": { "amount": 3300, "currency": "INR" } } ] }
def refund(self, amount=None): api_instance = TransactionsApi() api_instance.api_client.configuration.access_token = getattr( settings, 'SQUARE_ACCESS_TOKEN', '') transaction = self.getPayment() # For both partial and full refunds, we loop through the tenders and refund # them as much as possible until we've refunded all that we want to refund. if not amount: amount = sum([x.amount_money.amount / 100 for x in transaction.tenders or []]) - \ sum([x.amount_money.amount / 100 for x in transaction.refunds or []]) refundData = [] print('Beginning refund process.') remains_to_refund = amount tender_index = 0 while remains_to_refund > 0: idempotency_key = str(uuid.uuid1()) this_tender = transaction.tenders[tender_index] this_tender_refundamount = sum([ x.amount_money.amount / 100 for x in transaction.refunds or [] if x.tender_id == this_tender.id ]) to_refund = min( this_tender.amount_money.amount - this_tender_refundamount, remains_to_refund) body = { 'idempotency_key': idempotency_key, 'tender_id': this_tender.id, 'amount_money': { 'amount': int(to_refund * 100), 'currency': this_tender.amount_money.currency } } try: response = api_instance.create_refund( location_id=self.locationId, transaction_id=self.transactionId, body=body) if response.errors: logger.error('Error in providing Square refund: %s' % response.errors) refundData.append({ 'status': 'error', 'status': response.errors }) break except ApiException as e: logger.error('Error in providing Square refund.') refundData.append({'status': 'error', 'errors': e}) break print('Refund was successful? Data is: %s' % response) # Note that fees are often 0 or missing here, but we enqueue the task # retrieve and update them afterward. refundData.append({ 'status': 'success', 'refund_id': response.refund.id, 'refundAmount': float(response.refund.amount_money.amount) / 100, 'fees': float( getattr( getattr(response.refund, 'processing_fee_money', None), 'amount', 0)) / 100, }) remains_to_refund -= to_refund tender_index += 1 # Once the refund process is complete, fees will be calculated, # so schedule a task to get them and update records one minute # in the future. updateSquareFees.schedule(args=(self, ), delay=60) print('Ready to return: %s' % refundData) return refundData
def renewals_square(begin): scheduler.app.logger.info('Starting Square renewals') tomorrow = datetime.today() + timedelta(hours=24) failed_list = [] declined_list = [] with scheduler.app.app_context(): charge_list = User.query.filter( User.expiration < tomorrow, User.expiration > begin, User.square_id != None, User.role != None, ).all() if charge_list: square = Square.query.first() api_client = ApiClient() api_client.configuration.access_token = square.access_token transactions_api = TransactionsApi(api_client) for user in charge_list: idempotency_key = str(uuid.uuid1()) price_level = PriceLevel.query.filter_by( name=user.role).first() if price_level is None: failed_list.append(user) continue cents = price_level.price * 100 amount = {'amount': cents, 'currency': 'USD'} body = { 'idempotency_key': idempotency_key, 'customer_id': user.square_id, 'customer_card_id': user.square_card, 'amount_money': amount, } try: charge_response = transactions_api.charge( square.location_id, body) except Exception as e: scheduler.app.logger.info( f'{user.username} card declined {e}') declined_list.append(user) continue transaction = charge_response.transaction if transaction is None: scheduler.app.logger.info(f'{user.username} card declined') declined_list.append(user) continue elif transaction.id is None: scheduler.app.logger.info(f'{user.username} card declined') declined_list.append(user) continue else: if user.expiration <= datetime.today(): base = datetime.today() else: base = user.expiration user.expiration = base + timedelta(days=30) db.session.commit() send_failed_emails( scheduler.app, failed_list=failed_list, declined_list=declined_list, ) scheduler.app.logger.info('Square renewals complete')
def chargePayment(order, ccData, ipAddress): """ Returns two variabies: success - general success flag message - type of failure. """ try: idempotency_key = str(uuid.uuid1()) convertedTotal = int(order.total*100) amount = {'amount': convertedTotal, 'currency': settings.SQUARE_CURRENCY} billing_address = { 'postal_code' : ccData['card_data']['billing_postal_code'], } try: billing_address.update( {'address_line_1': ccData["address1"], 'address_line_2': ccData["address2"], 'locality': ccData["city"], 'administrative_district_level_1': ccData["state"], 'postal_code': ccData["postal"], 'country': ccData["country"], 'buyer_email_address': ccData["email"], 'first_name': ccData["cc_firstname"], 'last_name': ccData["cc_lastname"]} ) except KeyError as e: logger.debug("One or more billing address field omited - skipping") body = { 'idempotency_key': idempotency_key, 'card_nonce': ccData["nonce"], 'amount_money': amount, 'reference_id': order.reference, 'billing_address': billing_address } logger.debug("---- Begin Transaction ----") logger.debug(body) api_instance = TransactionsApi() api_instance.api_client.configuration.access_token = settings.SQUARE_ACCESS_TOKEN api_response = api_instance.charge(settings.SQUARE_LOCATION_ID, body) logger.debug("---- Charge Submitted ----") logger.debug(api_response) #try: #import pdb; pdb.set_trace() order.lastFour = api_response.transaction.tenders[0].card_details.card.last_4 order.apiData = json.dumps(api_response.to_dict()) order.notes = "Square: #" + api_response.transaction.id[:4] #except Exception as e: # logger.debug(dir(api_response)) # logger.exception(e) order.save() if api_response.errors and len(api_response.errors) > 0: message = api_response.errors[0].details logger.debug("---- Transaction Failed ----") return False, message logger.debug("---- End Transaction ----") return True, "" except ApiException as e: logger.debug("---- Transaction Failed ----") logger.error("!!Failed Square Transaction!!") logger.exception(e) logger.debug("---- End Transaction ----") try: return False, json.loads(e.body) except: return False, str(e)
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.' ))
location_id = config.get(config_type, "location_id") squareconnect.configuration.access_token = access_token # The ID of the business location to associate processed payments with. # See [Retrieve your business's locations] # (https://docs.connect.squareup.com/articles/getting-started/#retrievemerchantprofile) # for an easy way to get your business's location IDs. # If you're testing things out, use a sandbox location ID. if config.get("DEFAULT", "is_prod") == "true": location_id = config.get("PRODUCTION", "location_id") else: location_id = config.get("SANDBOX", "location_id") location_id = location_id api_instance = TransactionsApi() # Every payment you process with the SDK must have a unique idempotency key. # If you're unsure whether a particular payment succeeded, you can reattempt # it with the same idempotency key without worrying about double charging # the buyer. idempotency_key = str(uuid.uuid1()) # Monetary amounts are specified in the smallest unit of the applicable currency. # This amount is in cents. It's also hard-coded for $1.00, which isn't very useful. amount = {'amount': 100, 'currency': 'USD'} # To learn more about splitting transactions with additional recipients, # see the Transactions API documentation on our [developer site] # (https://docs.connect.squareup.com/payments/transactions/overview#mpt-overview). body = {
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)
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)
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.'))
def get_transactions_api(): return TransactionsApi(_sqapi_inst)